handlewhatsapp: add placeholders for missing events

This commit is contained in:
Tulir Asokan 2024-09-10 18:15:38 +03:00
parent 3c53ea5ed1
commit d4991cb0b6
5 changed files with 391 additions and 118 deletions

View file

@ -130,6 +130,8 @@ SELECT
-- json_object
(
'sender_device_id', CAST(nullif(split_part(replace(sender, '@s.whatsapp.net', ''), ':', 2), '') AS INTEGER)
'broadcast_list_jid', broadcast_list_jid,
'error', CAST(error AS TEXT),
) -- metadata
FROM message_old;

View file

@ -17,34 +17,34 @@ import (
)
func (wa *WhatsAppConnector) LoadUserLogin(_ context.Context, login *bridgev2.UserLogin) error {
w := &WhatsAppClient{
Main: wa,
UserLogin: login,
}
login.Client = w
loginMetadata := login.Metadata.(*UserLoginMetadata)
if loginMetadata.WADeviceID == 0 {
return nil
}
jid := waid.ParseUserLoginID(login.ID, loginMetadata.WADeviceID)
device, err := wa.DeviceStore.GetDevice(jid)
var err error
w.JID = waid.ParseUserLoginID(login.ID, loginMetadata.WADeviceID)
w.Device, err = wa.DeviceStore.GetDevice(w.JID)
if err != nil {
return err
}
w := &WhatsAppClient{
Main: wa,
UserLogin: login,
Device: device,
JID: jid,
}
if device != nil {
if w.Device != nil {
log := w.UserLogin.Log.With().Str("component", "whatsmeow").Logger()
w.Client = whatsmeow.NewClient(w.Device, waLog.Zerolog(log))
w.Client.AddEventHandler(w.handleWAEvent)
w.Client.AutomaticMessageRerequestFromPhone = true
w.Client.SetForceActiveDeliveryReceipts(wa.Config.ForceActiveDeliveryReceipts)
} else {
w.UserLogin.Log.Debug().Stringer("jid", jid).Msg("No device found for user in whatsmeow store")
w.UserLogin.Log.Warn().Stringer("jid", w.JID).Msg("No device found for user in whatsmeow store")
}
login.Client = w
return nil
}

View file

@ -2,6 +2,7 @@ package connector
import (
"go.mau.fi/util/jsontime"
"go.mau.fi/whatsmeow/types"
"maunium.net/go/mautrix/bridgev2/database"
)
@ -26,12 +27,24 @@ func (wa *WhatsAppConnector) GetDBMetaTypes() database.MetaTypes {
}
type UserLoginMetadata struct {
WADeviceID uint16 `json:"wa_device_id"`
//TODO: Add phone last ping/seen
WADeviceID uint16 `json:"wa_device_id"`
PhoneLastSeen jsontime.Unix `json:"phone_last_seen"`
PhoneLastPinged jsontime.Unix `json:"phone_last_pinged"`
Timezone string `json:"timezone"`
}
type MessageErrorType string
const (
MsgNoError MessageErrorType = ""
MsgErrDecryptionFailed MessageErrorType = "decryption_failed"
MsgErrMediaNotFound MessageErrorType = "media_not_found"
)
type MessageMetadata struct {
SenderDeviceID uint16 `json:"sender_device_id,omitempty"`
SenderDeviceID uint16 `json:"sender_device_id,omitempty"`
Error MessageErrorType `json:"error,omitempty"`
BroadcastListJID *types.JID `json:"broadcast_list_jid,omitempty"`
}
type ReactionMetadata struct {

View file

@ -11,17 +11,68 @@ import (
"maunium.net/go/mautrix/bridgev2"
"maunium.net/go/mautrix/bridgev2/database"
"maunium.net/go/mautrix/bridgev2/networkid"
"maunium.net/go/mautrix/event"
"maunium.net/go/mautrix/format"
"maunium.net/go/mautrix-whatsapp/pkg/waid"
)
func (wa *WhatsAppClient) getPortalKeyByMessageSource(ms types.MessageSource) networkid.PortalKey {
jid := ms.Chat
if ms.IsIncomingBroadcast() {
if ms.IsFromMe {
jid = ms.BroadcastListOwner.ToNonAD()
} else {
jid = ms.Sender.ToNonAD()
}
}
return wa.makeWAPortalKey(jid)
}
type MessageInfoWrapper struct {
Info types.MessageInfo
wa *WhatsAppClient
}
func (evt *MessageInfoWrapper) ShouldCreatePortal() bool {
return true
}
func (evt *MessageInfoWrapper) GetPortalKey() networkid.PortalKey {
return evt.wa.getPortalKeyByMessageSource(evt.Info.MessageSource)
}
func (evt *MessageInfoWrapper) AddLogContext(c zerolog.Context) zerolog.Context {
return c.Str("message_id", evt.Info.ID).Stringer("sender_id", evt.Info.Sender)
}
func (evt *MessageInfoWrapper) GetTimestamp() time.Time {
return evt.Info.Timestamp
}
func (evt *MessageInfoWrapper) GetSender() bridgev2.EventSender {
return evt.wa.makeEventSender(evt.Info.Sender)
}
func (evt *MessageInfoWrapper) GetID() networkid.MessageID {
return waid.MakeMessageID(evt.Info.Chat, evt.Info.Sender, evt.Info.ID)
}
func (evt *MessageInfoWrapper) GetTransactionID() networkid.TransactionID {
return networkid.TransactionID(evt.GetID())
}
type WAMessageEvent struct {
*events.Message
wa *WhatsAppClient
*MessageInfoWrapper
Message *waE2E.Message
MsgEvent *events.Message
isUndecryptableUpsertSubEvent bool
}
var (
_ bridgev2.RemoteMessage = (*WAMessageEvent)(nil)
_ bridgev2.RemoteMessageUpsert = (*WAMessageEvent)(nil)
_ bridgev2.RemoteMessageWithTransactionID = (*WAMessageEvent)(nil)
_ bridgev2.RemoteEventWithTimestamp = (*WAMessageEvent)(nil)
_ bridgev2.RemoteEventThatMayCreatePortal = (*WAMessageEvent)(nil)
@ -36,7 +87,13 @@ func (evt *WAMessageEvent) ConvertEdit(ctx context.Context, portal *bridgev2.Por
if len(existing) > 1 {
zerolog.Ctx(ctx).Warn().Msg("Got edit to message with multiple parts")
}
editedMsg := evt.Message.Message.GetProtocolMessage().GetEditedMessage()
var editedMsg *waE2E.Message
if evt.isUndecryptableUpsertSubEvent {
// TODO db metadata needs to be updated in this case to remove the error
editedMsg = evt.Message
} else {
editedMsg = evt.Message.GetProtocolMessage().GetEditedMessage()
}
// TODO edits to media captions may not contain the media
cm := evt.wa.Main.MsgConv.ToMatrix(ctx, portal, evt.wa.Client, intent, editedMsg)
@ -46,7 +103,7 @@ func (evt *WAMessageEvent) ConvertEdit(ctx context.Context, portal *bridgev2.Por
}
func (evt *WAMessageEvent) GetTargetMessage() networkid.MessageID {
if reactionMsg := evt.Message.Message.GetReactionMessage(); reactionMsg != nil {
if reactionMsg := evt.Message.GetReactionMessage(); reactionMsg != nil {
key := reactionMsg.GetKey()
senderID := key.GetParticipant()
if senderID == "" {
@ -58,7 +115,7 @@ func (evt *WAMessageEvent) GetTargetMessage() networkid.MessageID {
}
senderJID, _ := types.ParseJID(senderID)
return waid.MakeMessageID(evt.Info.Chat, senderJID, *key.ID)
} else if protocolMsg := evt.Message.Message.GetProtocolMessage(); protocolMsg != nil {
} else if protocolMsg := evt.Message.GetProtocolMessage(); protocolMsg != nil {
key := protocolMsg.GetKey()
senderID := key.GetParticipant()
if senderID == "" {
@ -75,7 +132,7 @@ func (evt *WAMessageEvent) GetTargetMessage() networkid.MessageID {
}
func (evt *WAMessageEvent) GetReactionEmoji() (string, networkid.EmojiID) {
return evt.Message.Message.GetReactionMessage().GetText(), ""
return evt.Message.GetReactionMessage().GetText(), ""
}
func (evt *WAMessageEvent) GetReactionDBMetadata() any {
@ -88,12 +145,8 @@ func (evt *WAMessageEvent) GetRemovedEmojiID() networkid.EmojiID {
return ""
}
func (evt *WAMessageEvent) ShouldCreatePortal() bool {
return true
}
func (evt *WAMessageEvent) GetType() bridgev2.RemoteEventType {
waMsg := evt.Message.Message
waMsg := evt.Message
if waMsg.ReactionMessage != nil {
if waMsg.ReactionMessage.GetText() == "" {
return bridgev2.RemoteEventReactionRemove
@ -107,40 +160,109 @@ func (evt *WAMessageEvent) GetType() bridgev2.RemoteEventType {
return bridgev2.RemoteEventEdit
}
}
return bridgev2.RemoteEventMessage
return bridgev2.RemoteEventMessageUpsert
}
func (evt *WAMessageEvent) GetPortalKey() networkid.PortalKey {
return evt.wa.makeWAPortalKey(evt.Info.Chat)
}
func (evt *WAMessageEvent) AddLogContext(c zerolog.Context) zerolog.Context {
return c.Str("message_id", evt.Info.ID).Stringer("sender_id", evt.Info.Sender)
}
func (evt *WAMessageEvent) GetTimestamp() time.Time {
return evt.Info.Timestamp
}
func (evt *WAMessageEvent) GetSender() bridgev2.EventSender {
return evt.wa.makeEventSender(evt.Info.Sender)
}
func (evt *WAMessageEvent) GetID() networkid.MessageID {
return waid.MakeMessageID(evt.Info.Chat, evt.Info.Sender, evt.Info.ID)
}
func (evt *WAMessageEvent) GetTransactionID() networkid.TransactionID {
// TODO for newsletter messages, there's a different transaction ID
return networkid.TransactionID(evt.GetID())
func (evt *WAMessageEvent) HandleExisting(ctx context.Context, portal *bridgev2.Portal, intent bridgev2.MatrixAPI, existing []*database.Message) (bridgev2.UpsertResult, error) {
if existing[0].Metadata.(*MessageMetadata).Error == MsgErrDecryptionFailed {
zerolog.Ctx(ctx).Debug().Stringer("existing_mxid", existing[0].MXID).Msg("Ignoring duplicate message")
evt.isUndecryptableUpsertSubEvent = true
return bridgev2.UpsertResult{SubEvents: []bridgev2.RemoteEvent{
&WANowDecryptableMessage{WAMessageEvent: evt, editParts: existing}},
}, nil
}
zerolog.Ctx(ctx).Debug().Stringer("existing_mxid", existing[0].MXID).Msg("Ignoring duplicate message")
return bridgev2.UpsertResult{}, nil
}
func (evt *WAMessageEvent) ConvertMessage(ctx context.Context, portal *bridgev2.Portal, intent bridgev2.MatrixAPI) (*bridgev2.ConvertedMessage, error) {
converted := evt.wa.Main.MsgConv.ToMatrix(ctx, portal, evt.wa.Client, intent, evt.Message.Message)
converted := evt.wa.Main.MsgConv.ToMatrix(ctx, portal, evt.wa.Client, intent, evt.Message)
for _, part := range converted.Parts {
part.DBMetadata = &MessageMetadata{
SenderDeviceID: evt.Info.Sender.Device,
}
if evt.Info.IsIncomingBroadcast() {
part.DBMetadata.(*MessageMetadata).BroadcastListJID = &evt.Info.Chat
if part.Extra == nil {
part.Extra = map[string]any{}
}
part.Extra["fi.mau.whatsapp.source_broadcast_list"] = evt.Info.Chat.String()
}
}
return converted, nil
}
type WANowDecryptableMessage struct {
*WAMessageEvent
editParts []*database.Message
}
var (
_ bridgev2.RemoteEdit = (*WANowDecryptableMessage)(nil)
_ bridgev2.RemoteEventWithBundledParts = (*WANowDecryptableMessage)(nil)
)
func (evt *WANowDecryptableMessage) GetTargetDBMessage() []*database.Message {
return evt.editParts
}
func (evt *WANowDecryptableMessage) GetTargetMessage() networkid.MessageID {
return evt.GetID()
}
func (evt *WANowDecryptableMessage) AddLogContext(c zerolog.Context) zerolog.Context {
return c
}
func (evt *WANowDecryptableMessage) GetType() bridgev2.RemoteEventType {
return bridgev2.RemoteEventMessage
}
type WAUndecryptableMessage struct {
*MessageInfoWrapper
}
var (
_ bridgev2.RemoteMessage = (*WAUndecryptableMessage)(nil)
_ bridgev2.RemoteMessageWithTransactionID = (*WAUndecryptableMessage)(nil)
_ bridgev2.RemoteEventWithTimestamp = (*WAUndecryptableMessage)(nil)
_ bridgev2.RemoteEventThatMayCreatePortal = (*WAUndecryptableMessage)(nil)
)
func (evt *WAUndecryptableMessage) GetType() bridgev2.RemoteEventType {
return bridgev2.RemoteEventMessage
}
const UndecryptableMessageNotice = "Decrypting message from WhatsApp failed, waiting for sender to re-send... " +
"([learn more](https://faq.whatsapp.com/general/security-and-privacy/seeing-waiting-for-this-message-this-may-take-a-while))"
var undecryptableMessageContent event.MessageEventContent
func init() {
undecryptableMessageContent = format.RenderMarkdown(UndecryptableMessageNotice, true, false)
undecryptableMessageContent.MsgType = event.MsgNotice
}
func (evt *WAUndecryptableMessage) ConvertMessage(ctx context.Context, portal *bridgev2.Portal, intent bridgev2.MatrixAPI) (*bridgev2.ConvertedMessage, error) {
extra := map[string]any{
"fi.mau.whatsapp.undecryptable": true,
}
var broadcastListJID *types.JID
if evt.Info.IsIncomingBroadcast() {
broadcastListJID = &evt.Info.Chat
extra["fi.mau.whatsapp.source_broadcast_list"] = evt.Info.Chat.String()
}
return &bridgev2.ConvertedMessage{
Parts: []*bridgev2.ConvertedMessagePart{{
Type: event.EventMessage,
Content: &undecryptableMessageContent,
Extra: extra,
DBMetadata: &MessageMetadata{
SenderDeviceID: evt.Info.Sender.Device,
Error: MsgErrDecryptionFailed,
BroadcastListJID: broadcastListJID,
},
}},
Disappear: portal.Disappear,
}, nil
}

View file

@ -53,74 +53,54 @@ func (wa *WhatsAppClient) handleWAEvent(rawEvt any) {
switch evt := rawEvt.(type) {
case *events.Message:
log.Trace().
Any("info", evt.Info).
Any("payload", evt.Message).
Msg("Received WhatsApp message")
if evt.Info.Chat.Server == types.HiddenUserServer || evt.Info.Sender.Server == types.HiddenUserServer {
return
}
wa.Main.Bridge.QueueRemoteEvent(wa.UserLogin, &WAMessageEvent{
Message: evt,
wa: wa,
})
wa.handleWAMessage(evt)
case *events.Receipt:
var evtType bridgev2.RemoteEventType
switch evt.Type {
case types.ReceiptTypeRead, types.ReceiptTypeReadSelf:
evtType = bridgev2.RemoteEventReadReceipt
case types.ReceiptTypeDelivered:
evtType = bridgev2.RemoteEventDeliveryReceipt
case types.ReceiptTypeSender:
fallthrough
default:
return
}
targets := make([]networkid.MessageID, len(evt.MessageIDs))
for i, id := range evt.MessageIDs {
targets[i] = waid.MakeMessageID(evt.Chat, *wa.Device.ID, id)
}
wa.Main.Bridge.QueueRemoteEvent(wa.UserLogin, &simplevent.Receipt{
EventMeta: simplevent.EventMeta{
Type: evtType,
PortalKey: wa.makeWAPortalKey(evt.Chat),
Sender: wa.makeEventSender(evt.Sender),
Timestamp: evt.Timestamp,
},
Targets: targets,
})
wa.handleWAReceipt(evt)
case *events.ChatPresence:
typingType := bridgev2.TypingTypeText
timeout := 15 * time.Second
if evt.Media == types.ChatPresenceMediaAudio {
typingType = bridgev2.TypingTypeRecordingMedia
}
if evt.State == types.ChatPresencePaused {
timeout = 0
}
wa.handleWAChatPresence(evt)
case *events.UndecryptableMessage:
wa.handleWAUndecryptableMessage(evt)
case *events.CallOffer:
wa.handleWACallStart(evt.CallCreator, evt.CallID, "", evt.Timestamp)
case *events.CallOfferNotice:
wa.handleWACallStart(evt.CallCreator, evt.CallID, evt.Type, evt.Timestamp)
case *events.CallTerminate, *events.CallRelayLatency, *events.CallAccept, *events.UnknownCallEvent:
// ignore
case *events.IdentityChange:
wa.handleWAIdentityChange(evt)
case *events.MarkChatAsRead:
// TODO
//wa.handleWAMarkChatAsRead(evt)
case *events.DeleteForMe:
// TODO
//wa.handleWADeleteForMe(evt)
case *events.DeleteChat:
// TODO
//wa.handleWADeleteChat(evt)
case *events.Mute:
// TODO
case *events.Archive:
// TODO
case *events.Pin:
// TODO
case *events.HistorySync:
// TODO
case *events.MediaRetry:
// TODO
case *events.GroupInfo:
// TODO
case *events.JoinedGroup:
// TODO
case *events.NewsletterJoin:
// TODO
case *events.NewsletterLeave:
// TODO
case *events.Picture:
// TODO
wa.Main.Bridge.QueueRemoteEvent(wa.UserLogin, &simplevent.Typing{
EventMeta: simplevent.EventMeta{
Type: bridgev2.RemoteEventTyping,
LogContext: nil,
PortalKey: wa.makeWAPortalKey(evt.Chat),
Sender: wa.makeEventSender(evt.Sender),
Timestamp: time.Now(),
},
Timeout: timeout,
Type: typingType,
})
case *events.Connected:
log.Debug().Msg("Connected to WhatsApp socket")
wa.UserLogin.BridgeState.Send(status.BridgeState{StateEvent: status.StateConnected})
if len(wa.Client.Store.PushName) > 0 {
go func() {
err := wa.Client.SendPresence(types.PresenceUnavailable)
if err != nil {
log.Warn().Err(err).Msg("Failed to send initial presence after connecting")
}
}()
}
case *events.AppStateSyncComplete:
if len(wa.Client.Store.PushName) > 0 && evt.Name == appstate.WAPatchCriticalBlock {
err := wa.Client.SendPresence(types.PresenceUnavailable)
@ -136,6 +116,8 @@ func (wa *WhatsAppClient) handleWAEvent(rawEvt any) {
//}
}()
}
case *events.AppState:
// Intentionally ignored
case *events.PushNameSetting:
// Send presence available when connecting and when the pushname is changed.
// This makes sure that outgoing messages always have the right pushname.
@ -149,6 +131,45 @@ func (wa *WhatsAppClient) handleWAEvent(rawEvt any) {
}
// TODO update own ghost info
//go user.syncPuppet(user.JID.ToNonAD(), "push name setting")
case *events.Contact:
// TODO
//go user.syncPuppet(v.JID, "contact event")
case *events.PushName:
// TODO
//go user.syncPuppet(v.JID, "push name event")
case *events.BusinessName:
// TODO
//go user.syncPuppet(v.JID, "business name event")
case *events.Connected:
log.Debug().Msg("Connected to WhatsApp socket")
wa.UserLogin.BridgeState.Send(status.BridgeState{StateEvent: status.StateConnected})
if len(wa.Client.Store.PushName) > 0 {
go func() {
err := wa.Client.SendPresence(types.PresenceUnavailable)
if err != nil {
log.Warn().Err(err).Msg("Failed to send initial presence after connecting")
}
}()
}
case *events.OfflineSyncPreview:
log.Info().
Int("message_count", evt.Messages).
Int("receipt_count", evt.Receipts).
Int("notification_count", evt.Notifications).
Int("app_data_change_count", evt.AppDataChanges).
Msg("Server sent number of events that were missed during downtime")
case *events.OfflineSyncCompleted:
if !wa.PhoneRecentlySeen(true) {
log.Info().
Time("phone_last_seen", wa.UserLogin.Metadata.(*UserLoginMetadata).PhoneLastSeen.Time).
Msg("Offline sync completed, but phone last seen date is still old - sending phone offline bridge status")
wa.UserLogin.BridgeState.Send(status.BridgeState{StateEvent: status.StateTransientDisconnect, Error: WAPhoneOffline})
} else {
log.Info().Msg("Offline sync completed")
}
case *events.LoggedOut:
wa.handleWALogout(evt.Reason, evt.OnConnect)
case *events.Disconnected:
// Don't send the normal transient disconnect state if we're already in a different transient disconnect state.
// TODO remove this if/when the phone offline state is moved to a sub-state of CONNECTED
@ -171,6 +192,11 @@ func (wa *WhatsAppClient) handleWAEvent(rawEvt any) {
})
case *events.StreamReplaced:
wa.UserLogin.BridgeState.Send(status.BridgeState{StateEvent: status.StateUnknownError, Error: WAStreamReplaced})
case *events.KeepAliveTimeout:
wa.UserLogin.BridgeState.Send(status.BridgeState{StateEvent: status.StateTransientDisconnect, Error: WAKeepaliveTimeout})
case *events.KeepAliveRestored:
log.Info().Msg("Keepalive restored after timeouts, sending connected event")
wa.UserLogin.BridgeState.Send(status.BridgeState{StateEvent: status.StateConnected})
case *events.ConnectFailure:
wa.UserLogin.BridgeState.Send(status.BridgeState{
StateEvent: status.StateUnknownError,
@ -190,3 +216,113 @@ func (wa *WhatsAppClient) handleWAEvent(rawEvt any) {
log.Debug().Type("event_type", rawEvt).Msg("Unhandled WhatsApp event")
}
}
func (wa *WhatsAppClient) handleWAMessage(evt *events.Message) {
wa.UserLogin.Log.Trace().
Any("info", evt.Info).
Any("payload", evt.Message).
Msg("Received WhatsApp message")
if evt.Info.Chat.Server == types.HiddenUserServer || evt.Info.Sender.Server == types.HiddenUserServer {
return
}
wa.Main.Bridge.QueueRemoteEvent(wa.UserLogin, &WAMessageEvent{
MessageInfoWrapper: &MessageInfoWrapper{
Info: evt.Info,
wa: wa,
},
Message: evt.Message,
MsgEvent: evt,
})
}
func (wa *WhatsAppClient) handleWAUndecryptableMessage(evt *events.UndecryptableMessage) {
wa.UserLogin.Log.Debug().
Any("info", evt.Info).
Bool("unavailable", evt.IsUnavailable).
Str("decrypt_fail", string(evt.DecryptFailMode)).
Msg("Received undecryptable WhatsApp message")
if evt.DecryptFailMode == events.DecryptFailHide || evt.Info.Chat.Server == types.HiddenUserServer || evt.Info.Sender.Server == types.HiddenUserServer {
return
}
wa.Main.Bridge.QueueRemoteEvent(wa.UserLogin, &WAUndecryptableMessage{
MessageInfoWrapper: &MessageInfoWrapper{
Info: evt.Info,
wa: wa,
},
})
}
func (wa *WhatsAppClient) handleWAReceipt(evt *events.Receipt) {
var evtType bridgev2.RemoteEventType
switch evt.Type {
case types.ReceiptTypeRead, types.ReceiptTypeReadSelf:
evtType = bridgev2.RemoteEventReadReceipt
case types.ReceiptTypeDelivered:
evtType = bridgev2.RemoteEventDeliveryReceipt
case types.ReceiptTypeSender:
fallthrough
default:
return
}
targets := make([]networkid.MessageID, len(evt.MessageIDs))
for i, id := range evt.MessageIDs {
targets[i] = waid.MakeMessageID(evt.Chat, *wa.Device.ID, id)
}
wa.Main.Bridge.QueueRemoteEvent(wa.UserLogin, &simplevent.Receipt{
EventMeta: simplevent.EventMeta{
Type: evtType,
PortalKey: wa.makeWAPortalKey(evt.Chat),
Sender: wa.makeEventSender(evt.Sender),
Timestamp: evt.Timestamp,
},
Targets: targets,
})
}
func (wa *WhatsAppClient) handleWAChatPresence(evt *events.ChatPresence) {
typingType := bridgev2.TypingTypeText
timeout := 15 * time.Second
if evt.Media == types.ChatPresenceMediaAudio {
typingType = bridgev2.TypingTypeRecordingMedia
}
if evt.State == types.ChatPresencePaused {
timeout = 0
}
wa.Main.Bridge.QueueRemoteEvent(wa.UserLogin, &simplevent.Typing{
EventMeta: simplevent.EventMeta{
Type: bridgev2.RemoteEventTyping,
LogContext: nil,
PortalKey: wa.makeWAPortalKey(evt.Chat),
Sender: wa.makeEventSender(evt.Sender),
Timestamp: time.Now(),
},
Timeout: timeout,
Type: typingType,
})
}
func (wa *WhatsAppClient) handleWALogout(reason events.ConnectFailureReason, onConnect bool) {
errorCode := WAUnknownLogout
if reason == events.ConnectFailureLoggedOut {
errorCode = WALoggedOut
} else if reason == events.ConnectFailureMainDeviceGone {
errorCode = WAMainDeviceGone
}
wa.Client.Disconnect()
wa.Client = nil
wa.JID = types.EmptyJID
wa.UserLogin.Metadata.(*UserLoginMetadata).WADeviceID = 0
wa.UserLogin.BridgeState.Send(status.BridgeState{
StateEvent: status.StateBadCredentials,
Error: errorCode,
})
}
func (wa *WhatsAppClient) handleWACallStart(sender types.JID, id, callType string, ts time.Time) {
// TODO
}
func (wa *WhatsAppClient) handleWAIdentityChange(evt *events.IdentityChange) {
// TODO
}