forked from MirrorHub/mautrix-whatsapp
Add support for asking homeserver for URL previews
This commit is contained in:
parent
d668c031f7
commit
10a7c781e6
8 changed files with 74 additions and 48 deletions
|
@ -5,11 +5,13 @@
|
|||
* (Re-)Added support for setting group avatar from Matrix.
|
||||
* Added initial support for re-fetching old media from phone.
|
||||
* Added support for bridging audio message waveforms in both directions.
|
||||
* Added support for sending URL previews to WhatsApp (both custom and autogenerated).
|
||||
* Fixed some issues with read receipt bridging
|
||||
* Fixed `!wa open` not working with new-style group IDs.
|
||||
* Fixed panic in disappearing message handling code if a portal is deleted with
|
||||
messages still inside.
|
||||
* Fixed disappearing message timer not being stored in post-login history sync.
|
||||
* Fixed formatting not being parsed in most incoming WhatsApp messages.
|
||||
|
||||
# v0.2.3 (2022-01-16)
|
||||
|
||||
|
|
|
@ -72,6 +72,7 @@ type BridgeConfig struct {
|
|||
WhatsappThumbnail bool `yaml:"whatsapp_thumbnail"`
|
||||
AllowUserInvite bool `yaml:"allow_user_invite"`
|
||||
FederateRooms bool `yaml:"federate_rooms"`
|
||||
URLPreviews bool `yaml:"url_previews"`
|
||||
|
||||
DisappearingMessagesInGroups bool `yaml:"disappearing_messages_in_groups"`
|
||||
|
||||
|
|
|
@ -104,6 +104,7 @@ func (helper *UpgradeHelper) doUpgrade() {
|
|||
helper.Copy(Bool, "bridge", "federate_rooms")
|
||||
helper.Copy(Bool, "bridge", "disappearing_messages_in_groups")
|
||||
helper.Copy(Bool, "bridge", "disable_bridge_alerts")
|
||||
helper.Copy(Bool, "bridge", "url_previews")
|
||||
helper.Copy(Str, "bridge", "management_room_text", "welcome")
|
||||
helper.Copy(Str, "bridge", "management_room_text", "welcome_connected")
|
||||
helper.Copy(Str, "bridge", "management_room_text", "welcome_unconnected")
|
||||
|
|
|
@ -194,6 +194,10 @@ bridge:
|
|||
# Should the bridge never send alerts to the bridge management room?
|
||||
# These are mostly things like the user being logged out.
|
||||
disable_bridge_alerts: false
|
||||
# Should the bridge detect URLs in outgoing messages, ask the homeserver to generate a preview,
|
||||
# and send it to WhatsApp? URL previews can always be sent using the `com.beeper.linkpreviews`
|
||||
# key in the event content even if this is disabled.
|
||||
url_previews: false
|
||||
|
||||
# The prefix for commands. Only required in non-management rooms.
|
||||
command_prefix: "!wa"
|
||||
|
|
7
go.mod
7
go.mod
|
@ -12,11 +12,12 @@ require (
|
|||
github.com/tidwall/gjson v1.14.0
|
||||
go.mau.fi/whatsmeow v0.0.0-20220211173754-90c655671ab0
|
||||
golang.org/x/image v0.0.0-20211028202545-6944b10bf410
|
||||
golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd
|
||||
google.golang.org/protobuf v1.27.1
|
||||
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b
|
||||
maunium.net/go/mauflag v1.0.0
|
||||
maunium.net/go/maulogger/v2 v2.3.2
|
||||
maunium.net/go/mautrix v0.10.11-0.20220215121349-628a694b037f
|
||||
maunium.net/go/mautrix v0.10.11-0.20220215142712-441b0812745a
|
||||
)
|
||||
|
||||
require (
|
||||
|
@ -33,8 +34,8 @@ require (
|
|||
github.com/tidwall/pretty v1.2.0 // indirect
|
||||
github.com/tidwall/sjson v1.2.4 // indirect
|
||||
go.mau.fi/libsignal v0.0.0-20211109153248-a67163214910 // indirect
|
||||
golang.org/x/crypto v0.0.0-20220213190939-1e6e3497d506 // indirect
|
||||
golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd // indirect
|
||||
golang.org/x/crypto v0.0.0-20220214200702-86341886e292 // indirect
|
||||
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e // indirect
|
||||
golang.org/x/text v0.3.7 // indirect
|
||||
gopkg.in/yaml.v2 v2.4.0 // indirect
|
||||
)
|
||||
|
|
8
go.sum
8
go.sum
|
@ -126,8 +126,9 @@ golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnf
|
|||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/crypto v0.0.0-20220213190939-1e6e3497d506 h1:EuGTJDfeg/PGZJp3gq1K+14eSLFTsrj1eg8KQuiUyKg=
|
||||
golang.org/x/crypto v0.0.0-20220213190939-1e6e3497d506/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||
golang.org/x/crypto v0.0.0-20220214200702-86341886e292 h1:f+lwQ+GtmgoY+A2YaQxlSOnDjXcQ7ZRLWOHbC6HtRqE=
|
||||
golang.org/x/crypto v0.0.0-20220214200702-86341886e292/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||
golang.org/x/image v0.0.0-20211028202545-6944b10bf410 h1:hTftEOvwiOq2+O8k2D5/Q7COC7k5Qcrgc2TFURJYnvQ=
|
||||
golang.org/x/image v0.0.0-20211028202545-6944b10bf410/go.mod h1:023OzeP/+EPmXeapQh35lcL3II3LrY8Ic+EFFKVhULM=
|
||||
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
|
@ -167,6 +168,7 @@ golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
|||
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk=
|
||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
|
||||
|
@ -199,5 +201,5 @@ 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/maulogger/v2 v2.3.2 h1:1XmIYmMd3PoQfp9J+PaHhpt80zpfmMqaShzUTC7FwY0=
|
||||
maunium.net/go/maulogger/v2 v2.3.2/go.mod h1:TYWy7wKwz/tIXTpsx8G3mZseIRiC5DoMxSZazOHy68A=
|
||||
maunium.net/go/mautrix v0.10.11-0.20220215121349-628a694b037f h1:07t3qJkxqu7iQZ7OUIba7NkwyfpD6ul8GOjsM+Nemh0=
|
||||
maunium.net/go/mautrix v0.10.11-0.20220215121349-628a694b037f/go.mod h1:Ynac6y32yvdJC8YiYvWjWp6u1WjVTNq+JssC+07ZZWw=
|
||||
maunium.net/go/mautrix v0.10.11-0.20220215142712-441b0812745a h1:qemTkoULb98wqW1CrV0qD1SQZ4rQw6HgmIuzYyJ3N64=
|
||||
maunium.net/go/mautrix v0.10.11-0.20220215142712-441b0812745a/go.mod h1:Ynac6y32yvdJC8YiYvWjWp6u1WjVTNq+JssC+07ZZWw=
|
||||
|
|
16
portal.go
16
portal.go
|
@ -2492,14 +2492,14 @@ func (portal *Portal) convertMatrixMessage(sender *User, evt *event.Event) (*waP
|
|||
if content.MsgType == event.MsgEmote && !relaybotFormatted {
|
||||
text = "/me " + text
|
||||
}
|
||||
if ctxInfo.StanzaId != nil || ctxInfo.MentionedJid != nil || ctxInfo.Expiration != nil || evt.Content.Raw["com.beeper.linkpreviews"] != nil {
|
||||
msg.ExtendedTextMessage = &waProto.ExtendedTextMessage{
|
||||
Text: &text,
|
||||
ContextInfo: &ctxInfo,
|
||||
}
|
||||
|
||||
portal.convertURLPreviewToWhatsApp(sender, evt, msg.ExtendedTextMessage)
|
||||
} else {
|
||||
msg.ExtendedTextMessage = &waProto.ExtendedTextMessage{
|
||||
Text: &text,
|
||||
ContextInfo: &ctxInfo,
|
||||
}
|
||||
hasPreview := portal.convertURLPreviewToWhatsApp(sender, evt, msg.ExtendedTextMessage)
|
||||
if ctxInfo.StanzaId == nil && ctxInfo.MentionedJid == nil && ctxInfo.Expiration == nil && !hasPreview {
|
||||
// No need for extended message
|
||||
msg.ExtendedTextMessage = nil
|
||||
msg.Conversation = &text
|
||||
}
|
||||
case event.MsgImage:
|
||||
|
|
|
@ -22,34 +22,27 @@ import (
|
|||
"encoding/json"
|
||||
"image"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"regexp"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/tidwall/gjson"
|
||||
"golang.org/x/net/idna"
|
||||
"google.golang.org/protobuf/proto"
|
||||
"maunium.net/go/mautrix/appservice"
|
||||
"maunium.net/go/mautrix/crypto/attachment"
|
||||
"maunium.net/go/mautrix/event"
|
||||
"maunium.net/go/mautrix/id"
|
||||
|
||||
"go.mau.fi/whatsmeow"
|
||||
waProto "go.mau.fi/whatsmeow/binary/proto"
|
||||
"maunium.net/go/mautrix"
|
||||
"maunium.net/go/mautrix/appservice"
|
||||
"maunium.net/go/mautrix/crypto/attachment"
|
||||
"maunium.net/go/mautrix/event"
|
||||
)
|
||||
|
||||
type BeeperLinkPreview struct {
|
||||
MatchedURL string `json:"matched_url"`
|
||||
CanonicalURL string `json:"og:url,omitempty"`
|
||||
Title string `json:"og:title,omitempty"`
|
||||
Type string `json:"og:type,omitempty"`
|
||||
Description string `json:"og:description,omitempty"`
|
||||
|
||||
ImageURL id.ContentURIString `json:"og:image,omitempty"`
|
||||
mautrix.RespPreviewURL
|
||||
MatchedURL string `json:"matched_url"`
|
||||
ImageEncryption *event.EncryptedFileInfo `json:"beeper:image:encryption,omitempty"`
|
||||
|
||||
ImageSize int `json:"matrix:image:size,omitempty"`
|
||||
ImageWidth int `json:"og:image:width,omitempty"`
|
||||
ImageHeight int `json:"og:image:height,omitempty"`
|
||||
ImageType string `json:"og:image:type,omitempty"`
|
||||
}
|
||||
|
||||
func (portal *Portal) convertURLPreviewToBeeper(intent *appservice.IntentAPI, source *User, msg *waProto.ExtendedTextMessage) []*BeeperLinkPreview {
|
||||
|
@ -58,10 +51,12 @@ func (portal *Portal) convertURLPreviewToBeeper(intent *appservice.IntentAPI, so
|
|||
}
|
||||
|
||||
output := &BeeperLinkPreview{
|
||||
MatchedURL: msg.GetMatchedText(),
|
||||
CanonicalURL: msg.GetCanonicalUrl(),
|
||||
Title: msg.GetTitle(),
|
||||
Description: msg.GetDescription(),
|
||||
MatchedURL: msg.GetMatchedText(),
|
||||
RespPreviewURL: mautrix.RespPreviewURL{
|
||||
CanonicalURL: msg.GetCanonicalUrl(),
|
||||
Title: msg.GetTitle(),
|
||||
Description: msg.GetDescription(),
|
||||
},
|
||||
}
|
||||
if len(output.CanonicalURL) == 0 {
|
||||
output.CanonicalURL = output.MatchedURL
|
||||
|
@ -115,19 +110,38 @@ func (portal *Portal) convertURLPreviewToBeeper(intent *appservice.IntentAPI, so
|
|||
return []*BeeperLinkPreview{output}
|
||||
}
|
||||
|
||||
func (portal *Portal) convertURLPreviewToWhatsApp(sender *User, evt *event.Event, dest *waProto.ExtendedTextMessage) {
|
||||
var URLRegex = regexp.MustCompile(`https?://[^\s/_*]+(?:/\S*)?`)
|
||||
|
||||
func (portal *Portal) convertURLPreviewToWhatsApp(sender *User, evt *event.Event, dest *waProto.ExtendedTextMessage) bool {
|
||||
var preview *BeeperLinkPreview
|
||||
|
||||
rawPreview := gjson.GetBytes(evt.Content.VeryRaw, `com\.beeper\.linkpreviews`)
|
||||
if !rawPreview.Exists() || !rawPreview.IsArray() {
|
||||
return
|
||||
if rawPreview.Exists() && rawPreview.IsArray() {
|
||||
var previews []BeeperLinkPreview
|
||||
if err := json.Unmarshal([]byte(rawPreview.Raw), &previews); err != nil || len(previews) == 0 {
|
||||
return false
|
||||
}
|
||||
// WhatsApp only supports a single preview.
|
||||
preview = &previews[0]
|
||||
} else if portal.bridge.Config.Bridge.URLPreviews {
|
||||
if matchedURL := URLRegex.FindString(evt.Content.AsMessage().Body); len(matchedURL) == 0 {
|
||||
return false
|
||||
} else if parsed, err := url.Parse(matchedURL); err != nil {
|
||||
return false
|
||||
} else if parsed.Host, err = idna.ToASCII(parsed.Host); err != nil {
|
||||
return false
|
||||
} else if mxPreview, err := portal.MainIntent().GetURLPreview(parsed.String()); err != nil {
|
||||
portal.log.Warnfln("Failed to fetch preview for %s: %v", matchedURL, err)
|
||||
return false
|
||||
} else {
|
||||
preview = &BeeperLinkPreview{
|
||||
RespPreviewURL: *mxPreview,
|
||||
MatchedURL: matchedURL,
|
||||
}
|
||||
}
|
||||
}
|
||||
var previews []BeeperLinkPreview
|
||||
if err := json.Unmarshal([]byte(rawPreview.Raw), &previews); err != nil || len(previews) == 0 {
|
||||
return
|
||||
}
|
||||
// WhatsApp only supports a single preview.
|
||||
preview := previews[0]
|
||||
if len(preview.MatchedURL) == 0 {
|
||||
return
|
||||
if preview == nil || len(preview.MatchedURL) == 0 {
|
||||
return false
|
||||
}
|
||||
|
||||
dest.MatchedText = &preview.MatchedURL
|
||||
|
@ -151,20 +165,20 @@ func (portal *Portal) convertURLPreviewToWhatsApp(sender *User, evt *event.Event
|
|||
data, err := portal.MainIntent().DownloadBytes(imageMXC)
|
||||
if err != nil {
|
||||
portal.log.Errorfln("Failed to download URL preview image %s in %s: %v", preview.ImageURL, evt.ID, err)
|
||||
return
|
||||
return true
|
||||
}
|
||||
if preview.ImageEncryption != nil {
|
||||
data, err = preview.ImageEncryption.Decrypt(data)
|
||||
if err != nil {
|
||||
portal.log.Errorfln("Failed to decrypt URL preview image in %s: %v", evt.ID, err)
|
||||
return
|
||||
return true
|
||||
}
|
||||
}
|
||||
dest.MediaKeyTimestamp = proto.Int64(time.Now().Unix())
|
||||
uploadResp, err := sender.Client.Upload(context.Background(), data, whatsmeow.MediaLinkThumbnail)
|
||||
if err != nil {
|
||||
portal.log.Errorfln("Failed to upload URL preview thumbnail in %s: %v", evt.ID, err)
|
||||
return
|
||||
return true
|
||||
}
|
||||
dest.ThumbnailSha256 = uploadResp.FileSHA256
|
||||
dest.ThumbnailEncSha256 = uploadResp.FileEncSHA256
|
||||
|
@ -183,4 +197,5 @@ func (portal *Portal) convertURLPreviewToWhatsApp(sender *User, evt *event.Event
|
|||
dest.ThumbnailHeight = proto.Uint32(uint32(height))
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue