Skip to content

Commit

Permalink
msgconv/from-whatsapp: add support for polls
Browse files Browse the repository at this point in the history
  • Loading branch information
tulir committed Oct 7, 2024
1 parent a351d2b commit 21f62e3
Show file tree
Hide file tree
Showing 10 changed files with 278 additions and 48 deletions.
4 changes: 2 additions & 2 deletions ROADMAP.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,8 @@
* [x] Location messages
* [x] Contact messages
* [x] Replies
* [ ] Polls
* [ ] Poll votes
* [x] Polls
* [x] Poll votes
* [ ] Chat types
* [x] Private chat
* [x] Group chat
Expand Down
1 change: 1 addition & 0 deletions cmd/mautrix-whatsapp/legacymigrate.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ func migrateLegacyConfig(helper up.Helper) {
bridgeconfig.CopyToOtherLocation(helper, up.Str, []string{"bridge", "status_broadcast_tag"}, []string{"network", "status_broadcast_tag"})
bridgeconfig.CopyToOtherLocation(helper, up.Bool, []string{"bridge", "whatsapp_thumbnail"}, []string{"network", "whatsapp_thumbnail"})
bridgeconfig.CopyToOtherLocation(helper, up.Bool, []string{"bridge", "url_previews"}, []string{"network", "url_previews"})
bridgeconfig.CopyToOtherLocation(helper, up.Bool, []string{"bridge", "extev_polls"}, []string{"network", "extev_polls"})
bridgeconfig.CopyToOtherLocation(helper, up.Bool, []string{"bridge", "force_active_delivery_receipts"}, []string{"network", "force_active_delivery_receipts"})
bridgeconfig.CopyToOtherLocation(helper, up.Int, []string{"bridge", "history_sync", "max_initial_conversations"}, []string{"network", "history_sync", "max_initial_conversations"})
bridgeconfig.CopyToOtherLocation(helper, up.Bool, []string{"bridge", "history_sync", "request_full_sync"}, []string{"network", "history_sync", "request_full_sync"})
Expand Down
2 changes: 2 additions & 0 deletions pkg/connector/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ type Config struct {
StatusBroadcastTag event.RoomTag `yaml:"status_broadcast_tag"`
WhatsappThumbnail bool `yaml:"whatsapp_thumbnail"`
URLPreviews bool `yaml:"url_previews"`
ExtEvPolls bool `yaml:"extev_polls"`
ForceActiveDeliveryReceipts bool `yaml:"force_active_delivery_receipts"`

AnimatedSticker msgconv.AnimatedStickerConfig `yaml:"animated_sticker"`
Expand Down Expand Up @@ -100,6 +101,7 @@ func upgradeConfig(helper up.Helper) {
helper.Copy(up.Str, "status_broadcast_tag")
helper.Copy(up.Bool, "whatsapp_thumbnail")
helper.Copy(up.Bool, "url_previews")
helper.Copy(up.Bool, "extev_polls")
helper.Copy(up.Bool, "force_active_delivery_receipts")

helper.Copy(up.Str, "animated_sticker", "target")
Expand Down
4 changes: 3 additions & 1 deletion pkg/connector/connector.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,8 +47,9 @@ func (wa *WhatsAppConnector) Init(bridge *bridgev2.Bridge) {
wa.Bridge = bridge
wa.MsgConv = msgconv.New(bridge)
wa.MsgConv.AnimatedStickerConfig = wa.Config.AnimatedSticker
wa.MsgConv.FetchURLPreviews = wa.Config.URLPreviews
wa.MsgConv.ExtEvPolls = wa.Config.ExtEvPolls
wa.MsgConv.OldMediaSuffix = "Requesting old media is not enabled on this bridge."
wa.MsgConv.FetchURLPreviews = wa.Config.URLPreviews
if wa.Config.HistorySync.MediaRequests.AutoRequestMedia {
if wa.Config.HistorySync.MediaRequests.RequestMethod == MediaRequestMethodImmediate {
wa.MsgConv.OldMediaSuffix = "Media will be requested from your phone automatically soon."
Expand All @@ -57,6 +58,7 @@ func (wa *WhatsAppConnector) Init(bridge *bridgev2.Bridge) {
}
}
wa.DB = wadb.New(bridge.ID, bridge.DB.Database, bridge.Log.With().Str("db_section", "whatsapp").Logger())
wa.MsgConv.DB = wa.DB
wa.Bridge.Commands.(*commands.Processor).AddHandlers(
cmdAccept,
)
Expand Down
2 changes: 2 additions & 0 deletions pkg/connector/example-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,8 @@ whatsapp_thumbnail: false
# 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
# Should polls be sent using unstable MSC3381 event types?
extev_polls: false
# Should the bridge always send "active" delivery receipts (two gray ticks on WhatsApp)
# even if the user isn't marked as online (e.g. when presence bridging isn't enabled)?
#
Expand Down
36 changes: 0 additions & 36 deletions pkg/connector/id.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,39 +50,3 @@ func (wa *WhatsAppClient) messageIDToKey(id *waid.ParsedMessageID) *waCommon.Mes
}
return key
}

//lint:ignore U1000 - TODO use this function
func (wa *WhatsAppClient) keyToMessageID(chat, sender types.JID, key *waCommon.MessageKey) networkid.MessageID {
sender = sender.ToNonAD()
var err error
if !key.GetFromMe() {
if key.GetParticipant() != "" {
sender, err = types.ParseJID(key.GetParticipant())
if err != nil {
// TODO log somehow?
return ""
}
if sender.Server == types.LegacyUserServer {
sender.Server = types.DefaultUserServer
}
} else if chat.Server == types.DefaultUserServer {
ownID := ptr.Val(wa.Device.ID).ToNonAD()
if sender.User == ownID.User {
sender = chat
} else {
sender = ownID
}
} else {
// TODO log somehow?
return ""
}
}
remoteJID, err := types.ParseJID(key.GetRemoteJID())
if err == nil && !remoteJID.IsEmpty() {
// TODO use remote jid in other cases?
if remoteJID.Server == types.GroupServer {
chat = remoteJID
}
}
return waid.MakeMessageID(chat, sender, key.GetID())
}
108 changes: 108 additions & 0 deletions pkg/msgconv/matrixpoll.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
// mautrix-whatsapp - A Matrix-WhatsApp puppeting bridge.
// Copyright (C) 2024 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 msgconv

import (
"reflect"

"maunium.net/go/mautrix/event"
)

var (
TypeMSC3381PollStart = event.Type{Class: event.MessageEventType, Type: "org.matrix.msc3381.poll.start"}
TypeMSC3381PollResponse = event.Type{Class: event.MessageEventType, Type: "org.matrix.msc3381.poll.response"}
)

type PollResponseContent struct {
RelatesTo event.RelatesTo `json:"m.relates_to"`
V1Response struct {
Answers []string `json:"answers"`
} `json:"org.matrix.msc3381.poll.response"`
V2Selections []string `json:"org.matrix.msc3381.v2.selections"`
}

func (content *PollResponseContent) GetRelatesTo() *event.RelatesTo {
return &content.RelatesTo
}

func (content *PollResponseContent) OptionalGetRelatesTo() *event.RelatesTo {
if content.RelatesTo.Type == "" {
return nil
}
return &content.RelatesTo
}

func (content *PollResponseContent) SetRelatesTo(rel *event.RelatesTo) {
content.RelatesTo = *rel
}

type MSC1767Message struct {
Text string `json:"org.matrix.msc1767.text,omitempty"`
HTML string `json:"org.matrix.msc1767.html,omitempty"`
Message []struct {
MimeType string `json:"mimetype"`
Body string `json:"body"`
} `json:"org.matrix.msc1767.message,omitempty"`
}

//lint:ignore U1000 Unused function
func msc1767ToWhatsApp(msg MSC1767Message) string {
for _, part := range msg.Message {
if part.MimeType == "text/html" && msg.HTML == "" {
msg.HTML = part.Body
} else if part.MimeType == "text/plain" && msg.Text == "" {
msg.Text = part.Body
}
}
if msg.HTML != "" {
return parseWAFormattingToHTML(msg.HTML, false)
}
return msg.Text
}

type PollStartContent struct {
RelatesTo *event.RelatesTo `json:"m.relates_to"`
PollStart struct {
Kind string `json:"kind"`
MaxSelections int `json:"max_selections"`
Question MSC1767Message `json:"question"`
Answers []struct {
ID string `json:"id"`
MSC1767Message
} `json:"answers"`
} `json:"org.matrix.msc3381.poll.start"`
}

func (content *PollStartContent) GetRelatesTo() *event.RelatesTo {
if content.RelatesTo == nil {
content.RelatesTo = &event.RelatesTo{}
}
return content.RelatesTo
}

func (content *PollStartContent) OptionalGetRelatesTo() *event.RelatesTo {
return content.RelatesTo
}

func (content *PollStartContent) SetRelatesTo(rel *event.RelatesTo) {
content.RelatesTo = rel
}

func init() {
event.TypeMap[TypeMSC3381PollResponse] = reflect.TypeOf(PollResponseContent{})
event.TypeMap[TypeMSC3381PollStart] = reflect.TypeOf(PollStartContent{})
}
4 changes: 4 additions & 0 deletions pkg/msgconv/msgconv.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ package msgconv
import (
"maunium.net/go/mautrix/bridgev2"
"maunium.net/go/mautrix/format"

"maunium.net/go/mautrix-whatsapp/pkg/connector/wadb"
)

type AnimatedStickerConfig struct {
Expand All @@ -32,10 +34,12 @@ type AnimatedStickerConfig struct {

type MessageConverter struct {
Bridge *bridgev2.Bridge
DB *wadb.Database
MaxFileSize int64
HTMLParser *format.HTMLParser
AnimatedStickerConfig AnimatedStickerConfig
FetchURLPreviews bool
ExtEvPolls bool
OldMediaSuffix string
}

Expand Down
Loading

0 comments on commit 21f62e3

Please sign in to comment.