mirror of
https://github.com/tulir/mautrix-whatsapp
synced 2024-12-13 17:13:11 +01:00
Add Matrix->WhatsApp EDU bridging
This commit is contained in:
parent
5346e01d2a
commit
bfe5af7edc
5 changed files with 114 additions and 30 deletions
|
@ -6,9 +6,9 @@
|
|||
* [x] Media/files
|
||||
* [x] Replies
|
||||
* [x] Message redactions
|
||||
* [ ] Presence<sup>[4]</sup>
|
||||
* [ ] Typing notifications<sup>[4]</sup>
|
||||
* [ ] Read receipts<sup>[4]</sup>
|
||||
* [x] Presence
|
||||
* [x] Typing notifications
|
||||
* [x] Read receipts
|
||||
* [ ] Power level
|
||||
* [ ] Membership actions
|
||||
* [ ] Invite
|
||||
|
@ -65,4 +65,3 @@
|
|||
<sup>[1]</sup> May involve reverse-engineering the WhatsApp Web API and/or editing go-whatsapp
|
||||
<sup>[2]</sup> May already work
|
||||
<sup>[3]</sup> May not be possible
|
||||
<sup>[4]</sup> Requires [matrix-org/synapse#2954](https://github.com/matrix-org/synapse/issues/2954) or Matrix puppeting
|
||||
|
|
107
custompuppet.go
107
custompuppet.go
|
@ -24,6 +24,7 @@ import (
|
|||
|
||||
"github.com/pkg/errors"
|
||||
|
||||
"github.com/Rhymen/go-whatsapp"
|
||||
"maunium.net/go/mautrix"
|
||||
"maunium.net/go/mautrix-appservice"
|
||||
)
|
||||
|
@ -77,30 +78,38 @@ func (puppet *Puppet) newCustomIntent() (*appservice.IntentAPI, error) {
|
|||
return ia, nil
|
||||
}
|
||||
|
||||
func (puppet *Puppet) clearCustomMXID() {
|
||||
puppet.CustomMXID = ""
|
||||
puppet.AccessToken = ""
|
||||
puppet.customIntent = nil
|
||||
puppet.customTypingIn = nil
|
||||
puppet.customUser = nil
|
||||
}
|
||||
|
||||
func (puppet *Puppet) StartCustomMXID() error {
|
||||
if len(puppet.CustomMXID) == 0 {
|
||||
puppet.clearCustomMXID()
|
||||
return nil
|
||||
}
|
||||
intent, err := puppet.newCustomIntent()
|
||||
if err != nil {
|
||||
puppet.CustomMXID = ""
|
||||
puppet.AccessToken = ""
|
||||
puppet.clearCustomMXID()
|
||||
return err
|
||||
}
|
||||
urlPath := intent.BuildURL("account", "whoami")
|
||||
var resp struct{ UserID string `json:"user_id"` }
|
||||
_, err = intent.MakeRequest("GET", urlPath, nil, &resp)
|
||||
if err != nil {
|
||||
puppet.CustomMXID = ""
|
||||
puppet.AccessToken = ""
|
||||
puppet.clearCustomMXID()
|
||||
return err
|
||||
}
|
||||
if resp.UserID != puppet.CustomMXID {
|
||||
puppet.CustomMXID = ""
|
||||
puppet.AccessToken = ""
|
||||
puppet.clearCustomMXID()
|
||||
return ErrMismatchingMXID
|
||||
}
|
||||
puppet.customIntent = intent
|
||||
puppet.customTypingIn = make(map[string]bool)
|
||||
puppet.customUser = puppet.bridge.GetUserByMXID(puppet.CustomMXID)
|
||||
puppet.startSyncing()
|
||||
return nil
|
||||
}
|
||||
|
@ -111,6 +120,7 @@ func (puppet *Puppet) startSyncing() {
|
|||
}
|
||||
go func() {
|
||||
puppet.log.Debugln("Starting syncing...")
|
||||
puppet.customIntent.SyncPresence = "offline"
|
||||
err := puppet.customIntent.Sync()
|
||||
if err != nil {
|
||||
puppet.log.Errorln("Fatal error syncing:", err)
|
||||
|
@ -126,12 +136,91 @@ func (puppet *Puppet) stopSyncing() {
|
|||
}
|
||||
|
||||
func (puppet *Puppet) ProcessResponse(resp *mautrix.RespSync, since string) error {
|
||||
d, _ := json.Marshal(resp)
|
||||
puppet.log.Debugln("Sync data:", string(d), since)
|
||||
// TODO handle sync data
|
||||
if !puppet.customUser.Connected {
|
||||
return fmt.Errorf("custom user not connected to whatsapp")
|
||||
}
|
||||
for roomID, events := range resp.Rooms.Join {
|
||||
portal := puppet.bridge.GetPortalByMXID(roomID)
|
||||
if portal == nil {
|
||||
continue
|
||||
}
|
||||
for _, event := range events.Ephemeral.Events {
|
||||
switch event.Type {
|
||||
case mautrix.EphemeralEventReceipt:
|
||||
go puppet.handleReceiptEvent(portal, event)
|
||||
case mautrix.EphemeralEventTyping:
|
||||
go puppet.handleTypingEvent(portal, event)
|
||||
}
|
||||
}
|
||||
}
|
||||
for _, event := range resp.Presence.Events {
|
||||
if event.Sender != puppet.CustomMXID {
|
||||
continue
|
||||
}
|
||||
go puppet.handlePresenceEvent(event)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (puppet *Puppet) handlePresenceEvent(event *mautrix.Event) {
|
||||
presence := whatsapp.PresenceAvailable
|
||||
if event.Content.Raw["presence"].(string) != "online" {
|
||||
presence = whatsapp.PresenceUnavailable
|
||||
puppet.customUser.log.Infoln("Marking offline")
|
||||
} else {
|
||||
puppet.customUser.log.Infoln("Marking online")
|
||||
}
|
||||
_, err := puppet.customUser.Conn.Presence("", presence)
|
||||
if err != nil {
|
||||
puppet.customUser.log.Warnln("Failed to set presence:", err)
|
||||
}
|
||||
}
|
||||
|
||||
func (puppet *Puppet) handleReceiptEvent(portal *Portal, event *mautrix.Event) {
|
||||
for eventID, rawReceipts := range event.Content.Raw {
|
||||
if receipts, ok := rawReceipts.(map[string]interface{}); !ok {
|
||||
continue
|
||||
} else if readReceipt, ok := receipts["m.read"].(map[string]interface{}); !ok {
|
||||
continue
|
||||
} else if _, ok = readReceipt[puppet.CustomMXID].(map[string]interface{}); !ok {
|
||||
continue
|
||||
}
|
||||
message := puppet.bridge.DB.Message.GetByMXID(eventID)
|
||||
if message == nil {
|
||||
continue
|
||||
}
|
||||
puppet.customUser.log.Infofln("Marking %s/%s in %s/%s as read", message.JID, message.MXID, portal.Key.JID, portal.MXID)
|
||||
_, err := puppet.customUser.Conn.Read(portal.Key.JID, message.JID)
|
||||
if err != nil {
|
||||
puppet.customUser.log.Warnln("Error marking read:", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (puppet *Puppet) handleTypingEvent(portal *Portal, event *mautrix.Event) {
|
||||
isTyping := false
|
||||
for _, userID := range event.Content.TypingUserIDs {
|
||||
if userID == puppet.CustomMXID {
|
||||
isTyping = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if puppet.customTypingIn[event.RoomID] != isTyping {
|
||||
puppet.customTypingIn[event.RoomID] = isTyping
|
||||
presence := whatsapp.PresenceComposing
|
||||
if !isTyping {
|
||||
puppet.customUser.log.Infofln("Marking not typing in %s/%s", portal.Key.JID, portal.MXID)
|
||||
presence = whatsapp.PresencePaused
|
||||
} else {
|
||||
puppet.customUser.log.Infofln("Marking typing in %s/%s", portal.Key.JID, portal.MXID)
|
||||
}
|
||||
_, err := puppet.customUser.Conn.Presence(portal.Key.JID, presence)
|
||||
if err != nil {
|
||||
puppet.customUser.log.Warnln("Error setting typing:", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (puppet *Puppet) OnFailedSync(res *mautrix.RespSync, err error) (time.Duration, error) {
|
||||
puppet.log.Warnln("Sync error:", err)
|
||||
return 10 * time.Second, nil
|
||||
|
|
|
@ -147,6 +147,8 @@ type Puppet struct {
|
|||
MXID types.MatrixUserID
|
||||
|
||||
customIntent *appservice.IntentAPI
|
||||
customTypingIn map[string]bool
|
||||
customUser *User
|
||||
}
|
||||
|
||||
func (puppet *Puppet) PhoneNumber() string {
|
||||
|
|
8
user.go
8
user.go
|
@ -344,7 +344,9 @@ func (user *User) updateLastConnectionIfNecessary() {
|
|||
}
|
||||
|
||||
func (user *User) HandleError(err error) {
|
||||
if err != whatsapp.ErrInvalidWsData {
|
||||
user.log.Errorln("WhatsApp error:", err)
|
||||
}
|
||||
var msg string
|
||||
if closed, ok := err.(*whatsapp.ErrConnectionClosed); ok {
|
||||
user.Connected = false
|
||||
|
@ -464,9 +466,9 @@ func (user *User) HandleMessageRevoke(message whatsappExt.MessageRevocation) {
|
|||
func (user *User) HandlePresence(info whatsappExt.Presence) {
|
||||
puppet := user.bridge.GetPuppetByJID(info.SenderJID)
|
||||
switch info.Status {
|
||||
case whatsappExt.PresenceUnavailable:
|
||||
case whatsapp.PresenceUnavailable:
|
||||
_ = puppet.DefaultIntent().SetPresence("offline")
|
||||
case whatsappExt.PresenceAvailable:
|
||||
case whatsapp.PresenceAvailable:
|
||||
if len(puppet.typingIn) > 0 && puppet.typingAt+15 > time.Now().Unix() {
|
||||
portal := user.bridge.GetPortalByMXID(puppet.typingIn)
|
||||
_, _ = puppet.IntentFor(portal).UserTyping(puppet.typingIn, false, 0)
|
||||
|
@ -475,7 +477,7 @@ func (user *User) HandlePresence(info whatsappExt.Presence) {
|
|||
} else {
|
||||
_ = puppet.DefaultIntent().SetPresence("online")
|
||||
}
|
||||
case whatsappExt.PresenceComposing:
|
||||
case whatsapp.PresenceComposing:
|
||||
portal := user.GetPortalByJID(info.JID)
|
||||
if len(puppet.typingIn) > 0 && puppet.typingAt+15 > time.Now().Unix() {
|
||||
if puppet.typingIn == portal.MXID {
|
||||
|
|
|
@ -23,18 +23,10 @@ import (
|
|||
"github.com/Rhymen/go-whatsapp"
|
||||
)
|
||||
|
||||
type PresenceType string
|
||||
|
||||
const (
|
||||
PresenceUnavailable PresenceType = "unavailable"
|
||||
PresenceAvailable PresenceType = "available"
|
||||
PresenceComposing PresenceType = "composing"
|
||||
)
|
||||
|
||||
type Presence struct {
|
||||
JID string `json:"id"`
|
||||
SenderJID string `json:"participant"`
|
||||
Status PresenceType `json:"type"`
|
||||
Status whatsapp.Presence `json:"type"`
|
||||
Timestamp int64 `json:"t"`
|
||||
Deny bool `json:"deny"`
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue