2018-08-13 20:24:44 +00:00
// mautrix-whatsapp - A Matrix-WhatsApp puppeting bridge.
2020-05-08 19:32:22 +00:00
// Copyright (C) 2020 Tulir Asokan
2018-08-13 20:24:44 +00:00
//
// 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 main
2018-08-16 12:59:18 +00:00
import (
2021-02-13 05:53:35 +00:00
"context"
2020-09-24 12:25:36 +00:00
"errors"
2019-05-15 20:04:09 +00:00
"fmt"
2020-08-22 10:07:55 +00:00
"net/http"
2021-02-21 05:58:50 +00:00
"sort"
2019-05-22 20:05:58 +00:00
"sync"
2018-08-16 12:59:18 +00:00
"time"
2018-08-24 16:46:14 +00:00
2019-01-11 19:17:31 +00:00
log "maunium.net/go/maulogger/v2"
2019-05-21 20:44:14 +00:00
"github.com/Rhymen/go-whatsapp"
2020-08-22 10:07:55 +00:00
"maunium.net/go/mautrix"
2020-05-08 19:32:22 +00:00
"maunium.net/go/mautrix/event"
2019-08-10 12:24:53 +00:00
"maunium.net/go/mautrix/format"
2020-05-08 19:32:22 +00:00
"maunium.net/go/mautrix/id"
2019-08-10 12:24:53 +00:00
2021-02-13 05:53:35 +00:00
"github.com/karmanyaahm/groupme"
2021-03-03 00:46:53 +00:00
"github.com/karmanyaahm/matrix-groupme-go/database"
"github.com/karmanyaahm/matrix-groupme-go/groupmeExt"
"github.com/karmanyaahm/matrix-groupme-go/types"
whatsappExt "github.com/karmanyaahm/matrix-groupme-go/whatsapp-ext"
2018-08-16 12:59:18 +00:00
)
type User struct {
* database . User
2021-02-13 05:53:35 +00:00
Conn * groupme . PushSubscription
2018-08-16 12:59:18 +00:00
bridge * Bridge
2018-08-16 16:20:07 +00:00
log log . Logger
2018-08-16 12:59:18 +00:00
2019-11-10 19:22:11 +00:00
Admin bool
Whitelisted bool
RelaybotWhitelisted bool
IsRelaybot bool
2019-05-17 20:53:57 +00:00
2021-02-21 05:58:50 +00:00
Client * groupmeExt . Client
2019-05-17 20:53:57 +00:00
ConnectionErrors int
2019-08-10 12:24:53 +00:00
CommunityID string
2019-05-22 20:05:58 +00:00
2021-02-28 19:59:03 +00:00
ChatList map [ types . GroupMeID ] groupme . Chat
GroupList map [ types . GroupMeID ] groupme . Group
2020-06-25 13:44:51 +00:00
cleanDisconnection bool
batteryWarningsSent int
2020-09-17 15:48:37 +00:00
lastReconnection int64
2019-07-04 12:08:58 +00:00
2019-08-30 17:57:08 +00:00
chatListReceived chan struct { }
syncPortalsDone chan struct { }
2019-08-24 21:25:29 +00:00
2020-11-06 13:52:16 +00:00
messageInput chan PortalMessage
messageOutput chan PortalMessage
2020-07-23 17:10:21 +00:00
syncStart chan struct { }
syncWait sync . WaitGroup
2020-05-27 09:16:05 +00:00
mgmtCreateLock sync . Mutex
2018-08-28 21:40:54 +00:00
}
2018-08-26 14:08:37 +00:00
2020-05-08 19:32:22 +00:00
func ( bridge * Bridge ) GetUserByMXID ( userID id . UserID ) * User {
2019-05-16 17:14:32 +00:00
_ , isPuppet := bridge . ParsePuppetMXID ( userID )
if isPuppet || userID == bridge . Bot . UserID {
return nil
}
2018-08-28 21:40:54 +00:00
bridge . usersLock . Lock ( )
defer bridge . usersLock . Unlock ( )
user , ok := bridge . usersByMXID [ userID ]
if ! ok {
2019-05-28 18:31:25 +00:00
return bridge . loadDBUser ( bridge . DB . User . GetByMXID ( userID ) , & userID )
2018-08-28 21:40:54 +00:00
}
return user
2018-08-16 12:59:18 +00:00
}
2021-02-13 05:53:35 +00:00
func ( bridge * Bridge ) GetUserByJID ( userID types . GroupMeID ) * User {
2018-08-28 21:40:54 +00:00
bridge . usersLock . Lock ( )
defer bridge . usersLock . Unlock ( )
user , ok := bridge . usersByJID [ userID ]
2018-08-16 12:59:18 +00:00
if ! ok {
2019-05-28 18:31:25 +00:00
return bridge . loadDBUser ( bridge . DB . User . GetByJID ( userID ) , nil )
2018-08-16 12:59:18 +00:00
}
return user
}
2020-05-21 16:49:01 +00:00
func ( user * User ) addToJIDMap ( ) {
user . bridge . usersLock . Lock ( )
user . bridge . usersByJID [ user . JID ] = user
user . bridge . usersLock . Unlock ( )
}
func ( user * User ) removeFromJIDMap ( ) {
user . bridge . usersLock . Lock ( )
2020-10-04 10:55:09 +00:00
jidUser , ok := user . bridge . usersByJID [ user . JID ]
if ok && user == jidUser {
delete ( user . bridge . usersByJID , user . JID )
}
2020-05-21 16:49:01 +00:00
user . bridge . usersLock . Unlock ( )
2020-09-27 19:30:08 +00:00
user . bridge . Metrics . TrackLoginState ( user . JID , false )
2020-05-21 16:49:01 +00:00
}
2018-08-16 12:59:18 +00:00
func ( bridge * Bridge ) GetAllUsers ( ) [ ] * User {
2018-08-28 21:40:54 +00:00
bridge . usersLock . Lock ( )
defer bridge . usersLock . Unlock ( )
2018-08-16 12:59:18 +00:00
dbUsers := bridge . DB . User . GetAll ( )
output := make ( [ ] * User , len ( dbUsers ) )
for index , dbUser := range dbUsers {
2018-08-28 21:40:54 +00:00
user , ok := bridge . usersByMXID [ dbUser . MXID ]
2018-08-16 12:59:18 +00:00
if ! ok {
2019-05-28 18:31:25 +00:00
user = bridge . loadDBUser ( dbUser , nil )
2018-08-16 12:59:18 +00:00
}
output [ index ] = user
}
return output
}
2020-05-08 19:32:22 +00:00
func ( bridge * Bridge ) loadDBUser ( dbUser * database . User , mxid * id . UserID ) * User {
2019-05-28 18:31:25 +00:00
if dbUser == nil {
if mxid == nil {
return nil
}
dbUser = bridge . DB . User . New ( )
dbUser . MXID = * mxid
dbUser . Insert ( )
}
user := bridge . NewUser ( dbUser )
bridge . usersByMXID [ user . MXID ] = user
if len ( user . JID ) > 0 {
bridge . usersByJID [ user . JID ] = user
}
if len ( user . ManagementRoom ) > 0 {
bridge . managementRooms [ user . ManagementRoom ] = user
}
return user
}
func ( user * User ) GetPortals ( ) [ ] * Portal {
2021-02-22 03:46:17 +00:00
user . bridge . portalsLock . Lock ( )
2019-05-28 18:31:25 +00:00
keys := user . User . GetPortalKeys ( )
portals := make ( [ ] * Portal , len ( keys ) )
for i , key := range keys {
portal , ok := user . bridge . portalsByJID [ key ]
if ! ok {
portal = user . bridge . loadDBPortal ( user . bridge . DB . Portal . GetByJID ( key ) , & key )
}
portals [ i ] = portal
}
2020-05-28 17:35:43 +00:00
user . bridge . portalsLock . Unlock ( )
2019-05-28 18:31:25 +00:00
return portals
}
2018-08-18 19:57:08 +00:00
func ( bridge * Bridge ) NewUser ( dbUser * database . User ) * User {
2018-08-25 21:26:24 +00:00
user := & User {
2019-01-11 19:17:31 +00:00
User : dbUser ,
bridge : bridge ,
log : bridge . Log . Sub ( "User" ) . Sub ( string ( dbUser . MXID ) ) ,
2019-05-22 20:05:58 +00:00
2019-11-10 19:22:11 +00:00
IsRelaybot : false ,
2019-08-30 17:57:08 +00:00
chatListReceived : make ( chan struct { } , 1 ) ,
2019-11-10 19:22:11 +00:00
syncPortalsDone : make ( chan struct { } , 1 ) ,
2020-07-23 17:10:21 +00:00
syncStart : make ( chan struct { } , 1 ) ,
2020-11-06 13:52:16 +00:00
messageInput : make ( chan PortalMessage ) ,
messageOutput : make ( chan PortalMessage , bridge . Config . Bridge . UserMessageBuffer ) ,
2018-08-16 12:59:18 +00:00
}
2019-11-10 19:22:11 +00:00
user . RelaybotWhitelisted = user . bridge . Config . Bridge . Permissions . IsRelaybotWhitelisted ( user . MXID )
2018-08-28 21:40:54 +00:00
user . Whitelisted = user . bridge . Config . Bridge . Permissions . IsWhitelisted ( user . MXID )
user . Admin = user . bridge . Config . Bridge . Permissions . IsAdmin ( user . MXID )
2019-05-22 20:05:58 +00:00
go user . handleMessageLoop ( )
2020-11-06 13:52:16 +00:00
go user . runMessageRingBuffer ( )
2018-08-25 21:26:24 +00:00
return user
2018-08-16 12:59:18 +00:00
}
2020-05-27 09:16:05 +00:00
func ( user * User ) GetManagementRoom ( ) id . RoomID {
if len ( user . ManagementRoom ) == 0 {
user . mgmtCreateLock . Lock ( )
defer user . mgmtCreateLock . Unlock ( )
if len ( user . ManagementRoom ) > 0 {
return user . ManagementRoom
}
resp , err := user . bridge . Bot . CreateRoom ( & mautrix . ReqCreateRoom {
Topic : "WhatsApp bridge notices" ,
IsDirect : true ,
} )
if err != nil {
user . log . Errorln ( "Failed to auto-create management room:" , err )
} else {
user . SetManagementRoom ( resp . RoomID )
}
}
return user . ManagementRoom
}
2020-05-08 19:32:22 +00:00
func ( user * User ) SetManagementRoom ( roomID id . RoomID ) {
2018-08-18 19:57:08 +00:00
existingUser , ok := user . bridge . managementRooms [ roomID ]
if ok {
existingUser . ManagementRoom = ""
existingUser . Update ( )
}
user . ManagementRoom = roomID
user . bridge . managementRooms [ user . ManagementRoom ] = user
user . Update ( )
}
func ( user * User ) SetSession ( session * whatsapp . Session ) {
2021-02-13 05:53:35 +00:00
// user.Session = session
// if session == nil {
// user.LastConnection = 0
// }
// user.Update()
2018-08-18 19:57:08 +00:00
}
2021-02-21 05:58:50 +00:00
func ( user * User ) Connect ( ) bool {
2018-08-18 19:57:08 +00:00
if user . Conn != nil {
return true
2021-02-21 05:58:50 +00:00
} else if len ( user . Token ) == 0 {
2018-08-18 19:57:08 +00:00
return false
}
2021-02-22 03:46:17 +00:00
2018-08-18 19:57:08 +00:00
user . log . Debugln ( "Connecting to WhatsApp" )
2019-05-16 15:08:30 +00:00
timeout := time . Duration ( user . bridge . Config . Bridge . ConnectionTimeout )
if timeout == 0 {
timeout = 20
}
2021-02-13 05:53:35 +00:00
conn := groupme . NewPushSubscription ( context . TODO ( ) )
user . Conn = & conn
2021-02-22 03:46:17 +00:00
user . Conn . StartListening ( context . TODO ( ) )
2021-02-13 05:53:35 +00:00
// if err != nil {
// user.log.Errorln("Failed to connect to WhatsApp:", err)
// user.sendMarkdownBridgeAlert("\u26a0 Failed to connect to WhatsApp server. " +
// "This indicates a network problem on the bridge server. See bridge logs for more info.")
// return false
// }
// user.Conn = whatsappExt.ExtendConn(conn)
// _ = user.Conn.SetClientName(user.bridge.Config.WhatsApp.OSName, user.bridge.Config.WhatsApp.BrowserName, WAVersion)
// user.log.Debugln("WhatsApp connection successful")
2021-02-22 03:46:17 +00:00
user . Conn . AddHandler ( user )
2021-02-13 05:53:35 +00:00
//TODO: typing notification?
2019-05-16 15:24:54 +00:00
return user . RestoreSession ( )
2018-08-16 12:59:18 +00:00
}
2018-08-18 19:57:08 +00:00
func ( user * User ) RestoreSession ( ) bool {
2021-02-13 05:53:35 +00:00
if len ( user . Token ) > 0 {
// if err == whatsapp.ErrAlreadyLoggedIn {
// return true
// } else if err != nil {
// user.log.Errorln("Failed to restore session:", err)
// if errors.Is(err, whatsapp.ErrUnpaired) {
// user.sendMarkdownBridgeAlert("\u26a0 Failed to connect to WhatsApp: unpaired from phone. " +
// "To re-pair your phone, use `delete-session` and then `login`.")
// } else {
// user.sendMarkdownBridgeAlert("\u26a0 Failed to connect to WhatsApp. Make sure WhatsApp " +
// "on your phone is reachable and use `reconnect` to try connecting again.")
// }
// user.log.Debugln("Disconnecting due to failed session restore...")
// _, err :=
// if err != nil {
// user.log.Errorln("Failed to disconnect after failed session restore:", err)
// }
// return false
// }
2021-02-22 03:46:17 +00:00
err := user . Conn . SubscribeToUser ( context . TODO ( ) , groupme . ID ( user . JID ) , user . Token )
if err != nil {
fmt . Println ( err )
}
2021-02-21 05:58:50 +00:00
//TODO: typing notifics
2019-05-17 20:53:57 +00:00
user . ConnectionErrors = 0
2021-02-13 05:53:35 +00:00
//user.SetSession(&sess)
2018-08-18 19:57:08 +00:00
user . log . Debugln ( "Session restored successfully" )
2019-05-30 14:48:22 +00:00
user . PostLogin ( )
2021-02-21 05:58:50 +00:00
return true
} else {
user . log . Debugln ( "tried login but no token" )
return false
2018-08-16 12:59:18 +00:00
}
}
2019-08-24 19:39:12 +00:00
func ( user * User ) HasSession ( ) bool {
2021-02-21 05:58:50 +00:00
return len ( user . Token ) > 0
2019-08-24 19:39:12 +00:00
}
func ( user * User ) IsConnected ( ) bool {
2021-02-21 05:58:50 +00:00
println ( "better connectoin check TODO" )
return user . Conn != nil
2018-08-28 21:40:54 +00:00
}
2021-02-13 05:53:35 +00:00
func ( user * User ) IsLoggedIn ( ) bool {
return true
}
2019-08-30 19:04:57 +00:00
func ( user * User ) IsLoginInProgress ( ) bool {
2021-02-13 05:53:35 +00:00
// return user.Conn != nil && user.Conn.IsLoginInProgress()
return false
}
func ( user * User ) GetJID ( ) types . GroupMeID {
if len ( user . JID ) == 0 {
u , _ := user . Client . MyUser ( context . TODO ( ) )
user . JID = u . ID . String ( )
}
return user . JID
2019-08-30 19:04:57 +00:00
}
2019-07-17 21:14:04 +00:00
func ( user * User ) Login ( ce * CommandEvent ) {
2021-02-13 05:53:35 +00:00
// qrChan := make(chan string, 3)
// eventIDChan := make(chan id.EventID, 1)
// go user.loginQrChannel(ce, qrChan, eventIDChan)
// session, err := user.Conn.LoginWithRetry(qrChan, user.bridge.Config.Bridge.LoginQRRegenCount)
// qrChan <- "stop"
// if err != nil {
// var eventID id.EventID
// select {
// case eventID = <-eventIDChan:
// default:
// }
// reply := event.MessageEventContent{
// MsgType: event.MsgText,
// }
// if err == whatsapp.ErrAlreadyLoggedIn {
// reply.Body = "You're already logged in"
// } else if err == whatsapp.ErrLoginInProgress {
// reply.Body = "You have a login in progress already."
// } else if err == whatsapp.ErrLoginTimedOut {
// reply.Body = "QR code scan timed out. Please try again."
// } else {
// user.log.Warnln("Failed to log in:", err)
// reply.Body = fmt.Sprintf("Unknown error while logging in: %v", err)
// }
// msg := reply
// if eventID != "" {
// msg.NewContent = &reply
// msg.RelatesTo = &event.RelatesTo{
// Type: event.RelReplace,
// EventID: eventID,
// }
// }
// _, _ = ce.Bot.SendMessageEvent(ce.RoomID, event.EventMessage, &msg)
// return
// }
// // TODO there's a bit of duplication between this and the provisioning API login method
// // Also between the two logout methods (commands.go and provisioning.go)
// user.ConnectionErrors = 0
// user.JID = strings.Replace(user.Conn.Info.Wid, whatsappExt.OldUserSuffix, whatsappExt.NewUserSuffix, 1)
2021-03-03 00:55:35 +00:00
if len ( ce . Args ) == 0 {
ce . Reply ( ` Get your access token from https://dev.groupme.com/ which should be the first argument to login ` )
return
}
2021-02-13 05:53:35 +00:00
user . Token = ce . Args [ 0 ]
2020-05-21 16:49:01 +00:00
user . addToJIDMap ( )
2021-02-13 05:53:35 +00:00
//user.SetSession(&session)
2019-05-22 13:46:18 +00:00
ce . Reply ( "Successfully logged in, synchronizing chats..." )
2021-02-21 05:58:50 +00:00
user . PostLogin ( )
2019-05-22 13:46:18 +00:00
}
type Chat struct {
Portal * Portal
LastMessageTime uint64
2021-02-21 05:58:50 +00:00
Group groupme . Group
2019-05-22 13:46:18 +00:00
}
type ChatList [ ] Chat
func ( cl ChatList ) Len ( ) int {
return len ( cl )
}
func ( cl ChatList ) Less ( i , j int ) bool {
2019-05-31 18:59:23 +00:00
return cl [ i ] . LastMessageTime > cl [ j ] . LastMessageTime
2019-05-22 13:46:18 +00:00
}
func ( cl ChatList ) Swap ( i , j int ) {
cl [ i ] , cl [ j ] = cl [ j ] , cl [ i ]
}
func ( user * User ) PostLogin ( ) {
2020-09-27 19:30:08 +00:00
user . bridge . Metrics . TrackConnectionState ( user . JID , true )
user . bridge . Metrics . TrackLoginState ( user . JID , true )
2020-11-16 12:28:08 +00:00
user . bridge . Metrics . TrackBufferLength ( user . MXID , 0 )
2019-08-10 12:24:53 +00:00
user . log . Debugln ( "Locking processing of incoming messages and starting post-login sync" )
2020-07-23 17:10:21 +00:00
user . syncWait . Add ( 1 )
user . syncStart <- struct { } { }
2019-05-30 14:48:22 +00:00
go user . intPostLogin ( )
}
2019-12-31 18:17:03 +00:00
func ( user * User ) tryAutomaticDoublePuppeting ( ) {
2020-05-08 19:32:22 +00:00
if len ( user . bridge . Config . Bridge . LoginSharedSecret ) == 0 {
// Automatic login not enabled
return
} else if _ , homeserver , _ := user . MXID . Parse ( ) ; homeserver != user . bridge . Config . Homeserver . Domain {
// user is on another homeserver
2019-12-30 18:21:04 +00:00
return
}
2020-11-06 00:29:14 +00:00
user . log . Debugln ( "Checking if double puppeting needs to be enabled" )
2019-12-30 18:21:04 +00:00
puppet := user . bridge . GetPuppetByJID ( user . JID )
if len ( puppet . CustomMXID ) > 0 {
2020-11-06 00:29:14 +00:00
user . log . Debugln ( "User already has double-puppeting enabled" )
2019-12-30 18:21:04 +00:00
// Custom puppet already enabled
return
}
accessToken , err := puppet . loginWithSharedSecret ( user . MXID )
if err != nil {
user . log . Warnln ( "Failed to login with shared secret:" , err )
return
}
err = puppet . SwitchCustomMXID ( accessToken , user . MXID )
if err != nil {
puppet . log . Warnln ( "Failed to switch to auto-logined custom puppet:" , err )
return
}
user . log . Infoln ( "Successfully automatically enabled custom puppet" )
}
2020-07-27 10:05:42 +00:00
func ( user * User ) sendBridgeNotice ( formatString string , args ... interface { } ) {
notice := fmt . Sprintf ( formatString , args ... )
_ , err := user . bridge . Bot . SendNotice ( user . GetManagementRoom ( ) , notice )
if err != nil {
user . log . Warnf ( "Failed to send bridge notice \"%s\": %v" , notice , err )
}
}
func ( user * User ) sendMarkdownBridgeAlert ( formatString string , args ... interface { } ) {
notice := fmt . Sprintf ( formatString , args ... )
content := format . RenderMarkdown ( notice , true , false )
_ , err := user . bridge . Bot . SendMessageEvent ( user . GetManagementRoom ( ) , event . EventMessage , content )
if err != nil {
user . log . Warnf ( "Failed to send bridge alert \"%s\": %v" , notice , err )
}
}
2020-08-05 19:06:54 +00:00
func ( user * User ) postConnPing ( ) bool {
2021-02-13 05:53:35 +00:00
// user.log.Debugln("Making post-connection ping")
// err := user.Conn.AdminTest()
// if err != nil {
// user.log.Errorfln("Post-connection ping failed: %v. Disconnecting and then reconnecting after a second", err)
// sess, disconnectErr := user.Conn.Disconnect()
// if disconnectErr != nil {
// user.log.Warnln("Error while disconnecting after failed post-connection ping:", disconnectErr)
// } else {
// user.Session = &sess
// }
// user.bridge.Metrics.TrackDisconnection(user.MXID)
// go func() {
// time.Sleep(1 * time.Second)
// user.tryReconnect(fmt.Sprintf("Post-connection ping failed: %v", err))
// }()
// return false
// } else {
// user.log.Debugln("Post-connection ping OK")
// return true
// }
return true
2020-08-05 19:06:54 +00:00
}
func ( user * User ) intPostLogin ( ) {
defer user . syncWait . Done ( )
2020-09-17 15:48:37 +00:00
user . lastReconnection = time . Now ( ) . Unix ( )
2021-02-21 05:58:50 +00:00
user . Client = groupmeExt . NewClient ( user . Token )
2021-02-22 03:46:17 +00:00
if len ( user . JID ) == 0 {
myuser , err := user . Client . MyUser ( context . TODO ( ) )
if err != nil {
log . Fatal ( err ) //TODO
}
user . JID = myuser . ID . String ( )
user . Update ( )
2021-02-21 05:58:50 +00:00
}
2020-08-05 19:06:54 +00:00
user . createCommunity ( )
user . tryAutomaticDoublePuppeting ( )
2020-07-27 10:06:03 +00:00
2020-11-06 00:29:14 +00:00
user . log . Debugln ( "Waiting for chat list receive confirmation" )
2021-02-21 05:58:50 +00:00
user . HandleChatList ( )
2019-08-24 21:25:29 +00:00
select {
2019-08-30 17:57:08 +00:00
case <- user . chatListReceived :
user . log . Debugln ( "Chat list receive confirmation received in PostLogin" )
case <- time . After ( time . Duration ( user . bridge . Config . Bridge . ChatListWait ) * time . Second ) :
2020-07-23 17:10:21 +00:00
user . log . Warnln ( "Timed out waiting for chat list to arrive!" )
2020-08-05 19:06:54 +00:00
user . postConnPing ( )
return
}
2020-11-06 00:29:14 +00:00
2020-08-05 19:06:54 +00:00
if ! user . postConnPing ( ) {
2020-11-06 00:29:14 +00:00
user . log . Debugln ( "Post-connection ping failed, unlocking processing of incoming messages." )
2019-08-30 17:57:08 +00:00
return
}
2020-11-06 00:29:14 +00:00
user . log . Debugln ( "Waiting for portal sync complete confirmation" )
2019-08-30 17:57:08 +00:00
select {
case <- user . syncPortalsDone :
2020-11-06 00:29:14 +00:00
user . log . Debugln ( "Post-connection portal sync complete, unlocking processing of incoming messages." )
2020-11-10 10:37:33 +00:00
// TODO this is too short, maybe a per-portal duration?
2019-08-30 17:57:08 +00:00
case <- time . After ( time . Duration ( user . bridge . Config . Bridge . PortalSyncWait ) * time . Second ) :
2020-07-23 17:10:21 +00:00
user . log . Warnln ( "Timed out waiting for portal sync to complete! Unlocking processing of incoming messages." )
2019-08-24 21:25:29 +00:00
}
2019-05-22 13:46:18 +00:00
}
2020-09-17 15:48:37 +00:00
func ( user * User ) HandleStreamEvent ( evt whatsappExt . StreamEvent ) {
if evt . Type == whatsappExt . StreamSleep {
if user . lastReconnection + 60 > time . Now ( ) . Unix ( ) {
user . lastReconnection = 0
user . log . Infoln ( "Stream went to sleep soon after reconnection, making new post-connection ping in 20 seconds" )
go func ( ) {
time . Sleep ( 20 * time . Second )
2020-12-27 22:22:40 +00:00
// TODO if this happens during the post-login sync, it can get stuck forever
2020-09-17 15:48:37 +00:00
user . postConnPing ( )
} ( )
}
} else {
user . log . Infofln ( "Stream event: %+v" , evt )
}
}
2021-02-21 05:58:50 +00:00
func ( user * User ) HandleChatList ( ) {
chatMap := make ( map [ string ] groupme . Group )
chats , err := user . Client . IndexAllGroups ( )
if err != nil {
log . Fatal ( err ) //TODO: handle
}
for _ , chat := range chats {
chatMap [ chat . ID . String ( ) ] = * chat
}
user . chatListReceived <- struct { } { }
user . log . Infoln ( "Chat list received" )
2021-02-28 19:59:03 +00:00
user . GroupList = chatMap
2021-02-21 05:58:50 +00:00
go user . syncPortals ( chatMap , false )
2019-08-24 21:25:29 +00:00
}
2021-02-21 05:58:50 +00:00
func ( user * User ) syncPortals ( chatMap map [ string ] groupme . Group , createAll bool ) {
if chatMap == nil {
// chatMap = user.Conn.Store.Chats
log . Fatal ( "chatmap nil major oops" )
}
user . log . Infoln ( "Reading chat list" )
chats := make ( ChatList , 0 , len ( chatMap ) )
existingKeys := user . GetInCommunityMap ( )
portalKeys := make ( [ ] database . PortalKeyWithMeta , 0 , len ( chatMap ) )
for _ , chat := range chatMap {
2021-02-28 20:01:32 +00:00
portal := user . bridge . GetPortalByJID ( database . GroupPortalKey ( chat . ID . String ( ) ) )
2021-02-21 05:58:50 +00:00
chats = append ( chats , Chat {
Portal : portal ,
2021-02-22 03:46:17 +00:00
LastMessageTime : uint64 ( chat . UpdatedAt . ToTime ( ) . Unix ( ) ) ,
2021-02-21 05:58:50 +00:00
Group : chat ,
} )
var inCommunity , ok bool
if inCommunity , ok = existingKeys [ portal . Key ] ; ! ok || ! inCommunity {
inCommunity = user . addPortalToCommunity ( portal )
if portal . IsPrivateChat ( ) {
puppet := user . bridge . GetPuppetByJID ( portal . Key . JID )
user . addPuppetToCommunity ( puppet )
}
}
portalKeys = append ( portalKeys , database . PortalKeyWithMeta { PortalKey : portal . Key , InCommunity : inCommunity } )
}
user . log . Infoln ( "Read chat list, updating user-portal mapping" )
2021-02-22 03:46:17 +00:00
2021-02-21 05:58:50 +00:00
err := user . SetPortalKeys ( portalKeys )
if err != nil {
user . log . Warnln ( "Failed to update user-portal mapping:" , err )
}
sort . Sort ( chats )
limit := user . bridge . Config . Bridge . InitialChatSync
if limit < 0 {
limit = len ( chats )
}
now := uint64 ( time . Now ( ) . Unix ( ) )
user . log . Infoln ( "Syncing portals" )
for i , chat := range chats {
if chat . LastMessageTime + user . bridge . Config . Bridge . SyncChatMaxAge < now {
break
}
create := ( chat . LastMessageTime >= user . LastConnection && user . LastConnection > 0 ) || i < limit
if len ( chat . Portal . MXID ) > 0 || create || createAll {
chat . Portal . Sync ( user , chat . Group )
err := chat . Portal . BackfillHistory ( user , chat . LastMessageTime )
if err != nil {
chat . Portal . log . Errorln ( "Error backfilling history:" , err )
}
}
}
2021-02-28 00:57:42 +00:00
//TODO: handle leave from groupme side
2021-02-21 05:58:50 +00:00
user . UpdateDirectChats ( nil )
user . log . Infoln ( "Finished syncing portals" )
select {
case user . syncPortalsDone <- struct { } { } :
default :
}
2019-05-22 13:46:18 +00:00
}
2020-08-22 10:07:55 +00:00
func ( user * User ) getDirectChats ( ) map [ id . UserID ] [ ] id . RoomID {
res := make ( map [ id . UserID ] [ ] id . RoomID )
privateChats := user . bridge . DB . Portal . FindPrivateChats ( user . JID )
for _ , portal := range privateChats {
if len ( portal . MXID ) > 0 {
res [ user . bridge . FormatPuppetMXID ( portal . Key . JID ) ] = [ ] id . RoomID { portal . MXID }
}
}
return res
}
func ( user * User ) UpdateDirectChats ( chats map [ id . UserID ] [ ] id . RoomID ) {
if ! user . bridge . Config . Bridge . SyncDirectChatList {
return
}
puppet := user . bridge . GetPuppetByCustomMXID ( user . MXID )
if puppet == nil || puppet . CustomIntent ( ) == nil {
return
}
intent := puppet . CustomIntent ( )
method := http . MethodPatch
if chats == nil {
chats = user . getDirectChats ( )
method = http . MethodPut
}
user . log . Debugln ( "Updating m.direct list on homeserver" )
var err error
if user . bridge . Config . Homeserver . Asmux {
urlPath := intent . BuildBaseURL ( "_matrix" , "client" , "unstable" , "net.maunium.asmux" , "dms" )
_ , err = intent . MakeFullRequest ( method , urlPath , http . Header {
"X-Asmux-Auth" : { user . bridge . AS . Registration . AppToken } ,
} , chats , nil )
} else {
existingChats := make ( map [ id . UserID ] [ ] id . RoomID )
err = intent . GetAccountData ( event . AccountDataDirectChats . Type , & existingChats )
if err != nil {
user . log . Warnln ( "Failed to get m.direct list to update it:" , err )
return
}
for userID , rooms := range existingChats {
if _ , ok := user . bridge . ParsePuppetMXID ( userID ) ; ! ok {
// This is not a ghost user, include it in the new list
chats [ userID ] = rooms
} else if _ , ok := chats [ userID ] ; ! ok && method == http . MethodPatch {
// This is a ghost user, but we're not replacing the whole list, so include it too
chats [ userID ] = rooms
}
}
err = intent . SetAccountData ( event . AccountDataDirectChats . Type , & chats )
}
if err != nil {
user . log . Warnln ( "Failed to update m.direct list:" , err )
}
}
2019-08-24 21:25:29 +00:00
func ( user * User ) HandleContactList ( contacts [ ] whatsapp . Contact ) {
contactMap := make ( map [ string ] whatsapp . Contact )
for _ , contact := range contacts {
contactMap [ contact . Jid ] = contact
}
go user . syncPuppets ( contactMap )
}
func ( user * User ) syncPuppets ( contacts map [ string ] whatsapp . Contact ) {
2021-02-13 05:53:35 +00:00
// if contacts == nil {
// contacts = user.Conn.Store.Contacts
// }
// user.log.Infoln("Syncing puppet info from contacts")
// for jid, contact := range contacts {
// if strings.HasSuffix(jid, whatsappExt.NewUserSuffix) {
// puppet := user.bridge.GetPuppetByJID(contact.Jid)
// puppet.Sync(user, contact)
// }
// }
// user.log.Infoln("Finished syncing puppet info from contacts")
2019-05-22 13:46:18 +00:00
}
func ( user * User ) updateLastConnectionIfNecessary ( ) {
if user . LastConnection + 60 < uint64 ( time . Now ( ) . Unix ( ) ) {
user . UpdateLastConnection ( )
}
2018-08-16 12:59:18 +00:00
}
func ( user * User ) HandleError ( err error ) {
2020-09-24 12:25:36 +00:00
if ! errors . Is ( err , whatsapp . ErrInvalidWsData ) {
2019-08-30 06:39:37 +00:00
user . log . Errorfln ( "WhatsApp error: %v" , err )
2019-05-27 10:46:04 +00:00
}
2019-05-17 20:53:57 +00:00
if closed , ok := err . ( * whatsapp . ErrConnectionClosed ) ; ok {
2020-06-17 14:57:14 +00:00
user . bridge . Metrics . TrackDisconnection ( user . MXID )
2019-07-04 12:08:58 +00:00
if closed . Code == 1000 && user . cleanDisconnection {
2020-09-27 19:30:08 +00:00
user . bridge . Metrics . TrackConnectionState ( user . JID , false )
2019-07-04 12:08:58 +00:00
user . cleanDisconnection = false
user . log . Infoln ( "Clean disconnection by server" )
2019-05-17 20:53:57 +00:00
return
2019-05-15 20:04:09 +00:00
}
2019-05-28 11:09:49 +00:00
go user . tryReconnect ( fmt . Sprintf ( "Your WhatsApp connection was closed with websocket status code %d" , closed . Code ) )
2019-05-17 20:53:57 +00:00
} else if failed , ok := err . ( * whatsapp . ErrConnectionFailed ) ; ok {
2020-06-17 14:57:14 +00:00
user . bridge . Metrics . TrackDisconnection ( user . MXID )
2019-05-17 20:53:57 +00:00
user . ConnectionErrors ++
2019-05-28 11:09:49 +00:00
go user . tryReconnect ( fmt . Sprintf ( "Your WhatsApp connection failed: %v" , failed . Err ) )
2019-05-15 20:04:09 +00:00
}
2019-05-28 11:09:49 +00:00
// Otherwise unknown error, probably mostly harmless
}
func ( user * User ) tryReconnect ( msg string ) {
2021-02-13 05:53:35 +00:00
// user.bridge.Metrics.TrackConnectionState(user.JID, false)
// if user.ConnectionErrors > user.bridge.Config.Bridge.MaxConnectionAttempts {
// user.sendMarkdownBridgeAlert("%s. Use the `reconnect` command to reconnect.", msg)
// return
// }
// if user.bridge.Config.Bridge.ReportConnectionRetry {
// user.sendBridgeNotice("%s. Reconnecting...", msg)
// // Don't want the same error to be repeated
// msg = ""
// }
// var tries uint
// var exponentialBackoff bool
// baseDelay := time.Duration(user.bridge.Config.Bridge.ConnectionRetryDelay)
// if baseDelay < 0 {
// exponentialBackoff = true
// baseDelay = -baseDelay + 1
// }
// delay := baseDelay
// for user.ConnectionErrors <= user.bridge.Config.Bridge.MaxConnectionAttempts {
// err := user.Conn.Restore()
// if err == nil {
// user.ConnectionErrors = 0
// if user.bridge.Config.Bridge.ReportConnectionRetry {
// user.sendBridgeNotice("Reconnected successfully")
// }
// user.PostLogin()
// return
// } else if err.Error() == "init responded with 400" {
// user.log.Infoln("Got init 400 error when trying to reconnect, resetting connection...")
// sess, err := user.Conn.Disconnect()
// if err != nil {
// user.log.Debugln("Error while disconnecting for connection reset:", err)
// }
// if len(sess.Wid) > 0 {
// user.SetSession(&sess)
// }
// }
// user.log.Errorln("Error while trying to reconnect after disconnection:", err)
// tries++
// user.ConnectionErrors++
// if user.ConnectionErrors <= user.bridge.Config.Bridge.MaxConnectionAttempts {
// if exponentialBackoff {
// delay = (1 << tries) + baseDelay
// }
// if user.bridge.Config.Bridge.ReportConnectionRetry {
// user.sendBridgeNotice("Reconnection attempt failed: %v. Retrying in %d seconds...", err, delay)
// }
// time.Sleep(delay * time.Second)
// }
// }
// if user.bridge.Config.Bridge.ReportConnectionRetry {
// user.sendMarkdownBridgeAlert("%d reconnection attempts failed. Use the `reconnect` command to try to reconnect manually.", tries)
// } else {
// user.sendMarkdownBridgeAlert("\u26a0 %s. Additionally, %d reconnection attempts failed. Use the `reconnect` command to try to reconnect.", msg, tries)
// }
2018-08-16 12:59:18 +00:00
}
2019-05-21 20:44:14 +00:00
func ( user * User ) ShouldCallSynchronously ( ) bool {
return true
}
2018-08-24 16:46:14 +00:00
func ( user * User ) HandleJSONParseError ( err error ) {
user . log . Errorln ( "WhatsApp JSON parse error:" , err )
}
2021-02-13 05:53:35 +00:00
func ( user * User ) PortalKey ( jid types . GroupMeID ) database . PortalKey {
2018-08-28 21:40:54 +00:00
return database . NewPortalKey ( jid , user . JID )
}
2021-02-13 05:53:35 +00:00
func ( user * User ) GetPortalByJID ( jid types . GroupMeID ) * Portal {
2018-08-28 21:40:54 +00:00
return user . bridge . GetPortalByJID ( user . PortalKey ( jid ) )
}
2020-11-06 13:52:16 +00:00
func ( user * User ) runMessageRingBuffer ( ) {
for msg := range user . messageInput {
select {
case user . messageOutput <- msg :
2020-11-16 12:28:08 +00:00
user . bridge . Metrics . TrackBufferLength ( user . MXID , len ( user . messageOutput ) )
2020-11-06 13:52:16 +00:00
default :
2020-11-06 13:56:07 +00:00
dropped := <- user . messageOutput
user . log . Warnln ( "Buffer is full, dropping message in" , dropped . chat )
2021-02-13 05:53:35 +00:00
user . messageOutput <- msg
2020-11-06 13:52:16 +00:00
}
}
}
2019-05-22 20:05:58 +00:00
func ( user * User ) handleMessageLoop ( ) {
2020-07-23 17:10:21 +00:00
for {
select {
2020-11-06 13:52:16 +00:00
case msg := <- user . messageOutput :
2020-11-16 12:28:08 +00:00
user . bridge . Metrics . TrackBufferLength ( user . MXID , len ( user . messageOutput ) )
2021-02-22 03:46:17 +00:00
puppet := user . bridge . GetPuppetByJID ( msg . data . UserID . String ( ) )
if puppet != nil {
2021-02-27 22:38:47 +00:00
puppet . Sync ( user , groupme . Member {
UserID : msg . data . UserID ,
Nickname : msg . data . Name ,
ImageURL : msg . data . AvatarURL ,
2021-02-22 03:46:17 +00:00
} ) //TODO: add params or docs?
}
2021-02-28 20:01:32 +00:00
user . bridge . GetPortalByJID ( database . GroupPortalKey ( msg . chat ) ) . messages <- msg
2020-07-23 17:10:21 +00:00
case <- user . syncStart :
2020-11-06 00:29:14 +00:00
user . log . Debugln ( "Processing of incoming messages is locked" )
2020-11-06 00:38:31 +00:00
user . bridge . Metrics . TrackSyncLock ( user . JID , true )
2020-07-23 17:10:21 +00:00
user . syncWait . Wait ( )
2020-11-06 00:38:31 +00:00
user . bridge . Metrics . TrackSyncLock ( user . JID , false )
2020-11-06 00:29:14 +00:00
user . log . Debugln ( "Processing of incoming messages unlocked" )
2020-07-23 17:10:21 +00:00
}
2019-05-22 20:05:58 +00:00
}
}
2021-02-22 03:46:17 +00:00
//func (user *User) HandleNewContact(contact whatsapp.Contact) {
// user.log.Debugfln("Contact message: %+v", contact)
// go func() {
// if strings.HasSuffix(contact.Jid, whatsappExt.OldUserSuffix) {
// contact.Jid = strings.Replace(contact.Jid, whatsappExt.OldUserSuffix, whatsappExt.NewUserSuffix, -1)
// }
// puppet := user.bridge.GetPuppetByJID(contact.Jid)
// puppet.UpdateName(user, contact)
// }()
//}
2020-06-25 13:44:51 +00:00
2021-02-22 03:46:17 +00:00
//func (user *User) HandleBatteryMessage(battery whatsapp.BatteryMessage) {
// user.log.Debugfln("Battery message: %+v", battery)
// var notice string
// if !battery.Plugged && battery.Percentage < 15 && user.batteryWarningsSent < 1 {
// notice = fmt.Sprintf("Phone battery low (%d %% remaining)", battery.Percentage)
// user.batteryWarningsSent = 1
// } else if !battery.Plugged && battery.Percentage < 5 && user.batteryWarningsSent < 2 {
// notice = fmt.Sprintf("Phone battery very low (%d %% remaining)", battery.Percentage)
// user.batteryWarningsSent = 2
// } else if battery.Percentage > 15 || battery.Plugged {
// user.batteryWarningsSent = 0
// }
// if notice != "" {
// go user.sendBridgeNotice("%s", notice)
// }
//}
2020-06-25 13:44:51 +00:00
2021-02-21 05:58:50 +00:00
func ( user * User ) HandleTextMessage ( message groupme . Message ) {
2021-02-28 20:01:32 +00:00
var group bool
var id string
if message . GroupID . String ( ) != "" {
group = true
id = message . GroupID . String ( )
} else if message . ConversationID . String ( ) != "" {
group = false
id = message . ConversationID . String ( )
} else {
user . log . Errorln ( "Message received without conversation or groupid" )
return
}
user . messageInput <- PortalMessage { id , group , user , & message , uint64 ( message . CreatedAt . ToTime ( ) . Unix ( ) ) }
2018-08-16 12:59:18 +00:00
}
2021-02-27 22:38:47 +00:00
func ( user * User ) HandleJoin ( id groupme . ID ) {
user . HandleChatList ( )
//TODO: efficient
}
2021-02-21 05:58:50 +00:00
//func (user *User) HandleImageMessage(message whatsapp.ImageMessage) {
// user.messageInput <- PortalMessage{message.Info.RemoteJid, user, message, message.Info.Timestamp}
//}
//
//func (user *User) HandleStickerMessage(message whatsapp.StickerMessage) {
// user.messageInput <- PortalMessage{message.Info.RemoteJid, user, message, message.Info.Timestamp}
//}
//
//func (user *User) HandleVideoMessage(message whatsapp.VideoMessage) {
// user.messageInput <- PortalMessage{message.Info.RemoteJid, user, message, message.Info.Timestamp}
//}
//
//func (user *User) HandleAudioMessage(message whatsapp.AudioMessage) {
// user.messageInput <- PortalMessage{message.Info.RemoteJid, user, message, message.Info.Timestamp}
//}
//
//func (user *User) HandleDocumentMessage(message whatsapp.DocumentMessage) {
// user.messageInput <- PortalMessage{message.Info.RemoteJid, user, message, message.Info.Timestamp}
//}
//
//func (user *User) HandleContactMessage(message whatsapp.ContactMessage) {
// user.messageInput <- PortalMessage{message.Info.RemoteJid, user, message, message.Info.Timestamp}
//}
//
//func (user *User) HandleLocationMessage(message whatsapp.LocationMessage) {
// user.messageInput <- PortalMessage{message.Info.RemoteJid, user, message, message.Info.Timestamp}
//}
//
//func (user *User) HandleMessageRevoke(message whatsappExt.MessageRevocation) {
// user.messageInput <- PortalMessage{message.RemoteJid, user, message, 0}
//}
2019-05-15 22:59:36 +00:00
2019-05-30 14:00:36 +00:00
type FakeMessage struct {
2020-05-21 17:14:43 +00:00
Text string
ID string
Alert bool
2019-05-30 14:00:36 +00:00
}
2021-02-21 05:58:50 +00:00
//func (user *User) HandleCallInfo(info whatsappExt.CallInfo) {
// if info.Data != nil {
// return
// }
// data := FakeMessage{
// ID: info.ID,
// }
// switch info.Type {
// case whatsappExt.CallOffer:
// if !user.bridge.Config.Bridge.CallNotices.Start {
// return
// }
// data.Text = "Incoming call"
// data.Alert = true
// case whatsappExt.CallOfferVideo:
// if !user.bridge.Config.Bridge.CallNotices.Start {
// return
// }
// data.Text = "Incoming video call"
// data.Alert = true
// case whatsappExt.CallTerminate:
// if !user.bridge.Config.Bridge.CallNotices.End {
// return
// }
// data.Text = "Call ended"
// data.ID += "E"
// default:
// return
// }
// portal := user.GetPortalByJID(info.From)
// if portal != nil {
// portal.messages <- PortalMessage{info.From, user, data, 0}
// }
//}
2019-05-30 14:00:36 +00:00
2021-02-22 03:46:17 +00:00
//func (user *User) HandlePresence(info whatsappExt.Presence) {
// puppet := user.bridge.GetPuppetByJID(info.SenderJID)
// switch info.Status {
// case whatsapp.PresenceUnavailable:
// _ = puppet.DefaultIntent().SetPresence("offline")
// 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)
// puppet.typingIn = ""
// puppet.typingAt = 0
// } else {
// _ = puppet.DefaultIntent().SetPresence("online")
// }
// case whatsapp.PresenceComposing:
// portal := user.GetPortalByJID(info.JID)
// if len(puppet.typingIn) > 0 && puppet.typingAt+15 > time.Now().Unix() {
// if puppet.typingIn == portal.MXID {
// return
// }
// _, _ = puppet.IntentFor(portal).UserTyping(puppet.typingIn, false, 0)
// }
// puppet.typingIn = portal.MXID
// puppet.typingAt = time.Now().Unix()
// _, _ = puppet.IntentFor(portal).UserTyping(portal.MXID, true, 15*1000)
// }
//}
//
//func (user *User) HandleMsgInfo(info whatsappExt.MsgInfo) {
// if (info.Command == whatsappExt.MsgInfoCommandAck || info.Command == whatsappExt.MsgInfoCommandAcks) && info.Acknowledgement == whatsappExt.AckMessageRead {
// portal := user.GetPortalByJID(info.ToJID)
// if len(portal.MXID) == 0 {
// return
// }
//
// go func() {
// intent := user.bridge.GetPuppetByJID(info.SenderJID).IntentFor(portal)
// for _, msgID := range info.IDs {
// msg := user.bridge.DB.Message.GetByJID(portal.Key, msgID)
// if msg == nil {
// continue
// }
//
// err := intent.MarkRead(portal.MXID, msg.MXID)
// if err != nil {
// user.log.Warnln("Failed to mark message %s as read by %s: %v", msg.MXID, info.SenderJID, err)
// }
// }
// }()
// }
//}
2018-08-25 21:39:36 +00:00
2021-02-22 03:46:17 +00:00
//func (user *User) HandleReceivedMessage(received whatsapp.ReceivedMessage) {
// if received.Type == "read" {
// user.markSelfRead(received.Jid, received.Index)
// } else {
// user.log.Debugfln("Unknown received message type: %+v", received)
// }
//}
//
//func (user *User) HandleReadMessage(read whatsapp.ReadMessage) {
// user.log.Debugfln("Received chat read message: %+v", read)
// user.markSelfRead(read.Jid, "")
//}
//
//func (user *User) markSelfRead(jid, messageID string) {
// if strings.HasSuffix(jid, whatsappExt.OldUserSuffix) {
// jid = strings.Replace(jid, whatsappExt.OldUserSuffix, whatsappExt.NewUserSuffix, -1)
// }
// puppet := user.bridge.GetPuppetByJID(user.JID)
// if puppet == nil {
// return
// }
// intent := puppet.CustomIntent()
// if intent == nil {
// return
// }
// portal := user.GetPortalByJID(jid)
// if portal == nil {
// return
// }
// var message *database.Message
// if messageID == "" {
// message = user.bridge.DB.Message.GetLastInChat(portal.Key)
// if message == nil {
// return
// }
// user.log.Debugfln("User read chat %s/%s in WhatsApp mobile (last known event: %s/%s)", portal.Key.JID, portal.MXID, message.JID, message.MXID)
// } else {
// message = user.bridge.DB.Message.GetByJID(portal.Key, messageID)
// if message == nil {
// return
// }
// user.log.Debugfln("User read message %s/%s in %s/%s in WhatsApp mobile", message.JID, message.MXID, portal.Key.JID, portal.MXID)
// }
// err := intent.MarkRead(portal.MXID, message.MXID)
// if err != nil {
// user.log.Warnfln("Failed to bridge own read receipt in %s: %v", jid, err)
// }
//}
//
//func (user *User) HandleCommand(cmd whatsappExt.Command) {
// switch cmd.Type {
// case whatsappExt.CommandPicture:
// if strings.HasSuffix(cmd.JID, whatsappExt.NewUserSuffix) {
// puppet := user.bridge.GetPuppetByJID(cmd.JID)
// go puppet.UpdateAvatar(user, cmd.ProfilePicInfo)
// } else {
// portal := user.GetPortalByJID(cmd.JID)
// go portal.UpdateAvatar(user, cmd.ProfilePicInfo, true)
// }
// case whatsappExt.CommandDisconnect:
// user.cleanDisconnection = true
// if cmd.Kind == "replaced" {
// go user.sendMarkdownBridgeAlert("\u26a0 Your WhatsApp connection was closed by the server because you opened another WhatsApp Web client.\n\n" +
// "Use the `reconnect` command to disconnect the other client and resume bridging.")
// } else {
// user.log.Warnln("Unknown kind of disconnect:", string(cmd.Raw))
// go user.sendMarkdownBridgeAlert("\u26a0 Your WhatsApp connection was closed by the server (reason code: %s).\n\n"+
// "Use the `reconnect` command to reconnect.", cmd.Kind)
// }
// }
//}
//
//func (user *User) HandleChatUpdate(cmd whatsappExt.ChatUpdate) {
// if cmd.Command != whatsappExt.ChatUpdateCommandAction {
// return
// }
//
// portal := user.GetPortalByJID(cmd.JID)
// if len(portal.MXID) == 0 {
// if cmd.Data.Action == whatsappExt.ChatActionIntroduce || cmd.Data.Action == whatsappExt.ChatActionCreate {
// go func() {
// err := portal.CreateMatrixRoom(user)
// if err != nil {
// user.log.Errorln("Failed to create portal room after receiving join event:", err)
// }
// }()
// }
// return
// }
//
// switch cmd.Data.Action {
// case whatsappExt.ChatActionNameChange:
// go portal.UpdateName(cmd.Data.NameChange.Name, cmd.Data.SenderJID, true)
// case whatsappExt.ChatActionAddTopic:
// go portal.UpdateTopic(cmd.Data.AddTopic.Topic, cmd.Data.SenderJID, true)
// case whatsappExt.ChatActionRemoveTopic:
// go portal.UpdateTopic("", cmd.Data.SenderJID, true)
// case whatsappExt.ChatActionPromote:
// go portal.ChangeAdminStatus(cmd.Data.UserChange.JIDs, true)
// case whatsappExt.ChatActionDemote:
// go portal.ChangeAdminStatus(cmd.Data.UserChange.JIDs, false)
// case whatsappExt.ChatActionAnnounce:
// go portal.RestrictMessageSending(cmd.Data.Announce)
// case whatsappExt.ChatActionRestrict:
// go portal.RestrictMetadataChanges(cmd.Data.Restrict)
// case whatsappExt.ChatActionRemove:
// go portal.HandleWhatsAppKick(cmd.Data.SenderJID, cmd.Data.UserChange.JIDs)
// case whatsappExt.ChatActionAdd:
// go portal.HandleWhatsAppInvite(cmd.Data.SenderJID, cmd.Data.UserChange.JIDs)
// //case whatsappExt.ChatActionIntroduce:
// // if cmd.Data.SenderJID != "unknown" {
// // go portal.Sync(user, whatsapp.Contact{Jid: portal.Key.JID})
// // }
// }
//}
2019-05-22 13:46:18 +00:00
2021-02-22 03:46:17 +00:00
//func (user *User) HandleJsonMessage(message string) {
// var msg json.RawMessage
// err := json.Unmarshal([]byte(message), &msg)
// if err != nil {
// return
// }
// user.log.Debugln("JSON message:", message)
// user.updateLastConnectionIfNecessary()
//}
2019-11-10 19:22:11 +00:00
2021-02-22 03:46:17 +00:00
//func (user *User) HandleRawMessage(message *waProto.WebMessageInfo) {
// user.updateLastConnectionIfNecessary()
//}
//
//func (user *User) HandleUnknownBinaryNode(node *waBinary.Node) {
// user.log.Debugfln("Unknown binary message: %+v", node)
//}
2020-07-30 15:08:26 +00:00
2019-11-10 19:22:11 +00:00
func ( user * User ) NeedsRelaybot ( portal * Portal ) bool {
2019-11-11 20:41:58 +00:00
return ! user . HasSession ( ) || ! user . IsInPortal ( portal . Key )
2019-11-10 19:22:11 +00:00
}