Handle decryption errors from WhatsApp properly

This commit is contained in:
Tulir Asokan 2021-10-27 18:31:33 +03:00
parent ded2fb9799
commit b918b4f261
5 changed files with 166 additions and 61 deletions

View file

@ -40,12 +40,34 @@ func (mq *MessageQuery) New() *Message {
} }
} }
const (
getAllMessagesQuery = `
SELECT chat_jid, chat_receiver, jid, mxid, sender, timestamp, sent, decryption_error FROM message
WHERE chat_jid=$1 AND chat_receiver=$2
`
getMessageByJIDQuery = `
SELECT chat_jid, chat_receiver, jid, mxid, sender, timestamp, sent, decryption_error FROM message
WHERE chat_jid=$1 AND chat_receiver=$2 AND jid=$3
`
getMessageByMXIDQuery = `
SELECT chat_jid, chat_receiver, jid, mxid, sender, timestamp, sent, decryption_error FROM message
WHERE mxid=$1
`
getLastMessageInChatQuery = `
SELECT chat_jid, chat_receiver, jid, mxid, sender, timestamp, sent, decryption_error FROM message
WHERE chat_jid=$1 AND chat_receiver=$2 AND timestamp<=$3 AND sent=true ORDER BY timestamp DESC LIMIT 1
`
getFirstMessageInChatQuery = `
SELECT chat_jid, chat_receiver, jid, mxid, sender, timestamp, sent, decryption_error FROM message
WHERE chat_jid=$1 AND chat_receiver=$2 AND sent=true ORDER BY timestamp ASC LIMIT 1
`
)
func (mq *MessageQuery) GetAll(chat PortalKey) (messages []*Message) { func (mq *MessageQuery) GetAll(chat PortalKey) (messages []*Message) {
rows, err := mq.db.Query("SELECT chat_jid, chat_receiver, jid, mxid, sender, timestamp, sent FROM message WHERE chat_jid=$1 AND chat_receiver=$2", chat.JID, chat.Receiver) rows, err := mq.db.Query(getAllMessagesQuery, chat.JID, chat.Receiver)
if err != nil || rows == nil { if err != nil || rows == nil {
return nil return nil
} }
defer rows.Close()
for rows.Next() { for rows.Next() {
messages = append(messages, mq.New().Scan(rows)) messages = append(messages, mq.New().Scan(rows))
} }
@ -53,23 +75,19 @@ func (mq *MessageQuery) GetAll(chat PortalKey) (messages []*Message) {
} }
func (mq *MessageQuery) GetByJID(chat PortalKey, jid types.MessageID) *Message { func (mq *MessageQuery) GetByJID(chat PortalKey, jid types.MessageID) *Message {
return mq.get("SELECT chat_jid, chat_receiver, jid, mxid, sender, timestamp, sent "+ return mq.maybeScan(mq.db.QueryRow(getMessageByJIDQuery, chat.JID, chat.Receiver, jid))
"FROM message WHERE chat_jid=$1 AND chat_receiver=$2 AND jid=$3", chat.JID, chat.Receiver, jid)
} }
func (mq *MessageQuery) GetByMXID(mxid id.EventID) *Message { func (mq *MessageQuery) GetByMXID(mxid id.EventID) *Message {
return mq.get("SELECT chat_jid, chat_receiver, jid, mxid, sender, timestamp, sent "+ return mq.maybeScan(mq.db.QueryRow(getMessageByMXIDQuery, mxid))
"FROM message WHERE mxid=$1", mxid)
} }
func (mq *MessageQuery) GetLastInChat(chat PortalKey) *Message { func (mq *MessageQuery) GetLastInChat(chat PortalKey) *Message {
return mq.GetLastInChatBefore(chat, time.Now().Add(60 * time.Second)) return mq.GetLastInChatBefore(chat, time.Now().Add(60*time.Second))
} }
func (mq *MessageQuery) GetLastInChatBefore(chat PortalKey, maxTimestamp time.Time) *Message { func (mq *MessageQuery) GetLastInChatBefore(chat PortalKey, maxTimestamp time.Time) *Message {
msg := mq.get("SELECT chat_jid, chat_receiver, jid, mxid, sender, timestamp, sent "+ msg := mq.maybeScan(mq.db.QueryRow(getLastMessageInChatQuery, chat.JID, chat.Receiver, maxTimestamp.Unix()))
"FROM message WHERE chat_jid=$1 AND chat_receiver=$2 AND timestamp<=$3 AND sent=true ORDER BY timestamp DESC LIMIT 1",
chat.JID, chat.Receiver, maxTimestamp.Unix())
if msg == nil || msg.Timestamp.IsZero() { if msg == nil || msg.Timestamp.IsZero() {
// Old db, we don't know what the last message is. // Old db, we don't know what the last message is.
return nil return nil
@ -78,13 +96,10 @@ func (mq *MessageQuery) GetLastInChatBefore(chat PortalKey, maxTimestamp time.Ti
} }
func (mq *MessageQuery) GetFirstInChat(chat PortalKey) *Message { func (mq *MessageQuery) GetFirstInChat(chat PortalKey) *Message {
return mq.get("SELECT chat_jid, chat_receiver, jid, mxid, sender, timestamp, sent "+ return mq.maybeScan(mq.db.QueryRow(getFirstMessageInChatQuery, chat.JID, chat.Receiver))
"FROM message WHERE chat_jid=$1 AND chat_receiver=$2 AND sent=true ORDER BY timestamp ASC LIMIT 1",
chat.JID, chat.Receiver)
} }
func (mq *MessageQuery) get(query string, args ...interface{}) *Message { func (mq *MessageQuery) maybeScan(row *sql.Row) *Message {
row := mq.db.QueryRow(query, args...)
if row == nil { if row == nil {
return nil return nil
} }
@ -101,6 +116,8 @@ type Message struct {
Sender types.JID Sender types.JID
Timestamp time.Time Timestamp time.Time
Sent bool Sent bool
DecryptionError bool
} }
func (msg *Message) IsFakeMXID() bool { func (msg *Message) IsFakeMXID() bool {
@ -109,7 +126,7 @@ func (msg *Message) IsFakeMXID() bool {
func (msg *Message) Scan(row Scannable) *Message { func (msg *Message) Scan(row Scannable) *Message {
var ts int64 var ts int64
err := row.Scan(&msg.Chat.JID, &msg.Chat.Receiver, &msg.JID, &msg.MXID, &msg.Sender, &ts, &msg.Sent) err := row.Scan(&msg.Chat.JID, &msg.Chat.Receiver, &msg.JID, &msg.MXID, &msg.Sender, &ts, &msg.Sent, &msg.DecryptionError)
if err != nil { if err != nil {
if err != sql.ErrNoRows { if err != sql.ErrNoRows {
msg.log.Errorln("Database scan failed:", err) msg.log.Errorln("Database scan failed:", err)
@ -129,9 +146,9 @@ func (msg *Message) Insert() {
sender = "" sender = ""
} }
_, err := msg.db.Exec(`INSERT INTO message _, err := msg.db.Exec(`INSERT INTO message
(chat_jid, chat_receiver, jid, mxid, sender, timestamp, sent) (chat_jid, chat_receiver, jid, mxid, sender, timestamp, sent, decryption_error)
VALUES ($1, $2, $3, $4, $5, $6, $7)`, VALUES ($1, $2, $3, $4, $5, $6, $7, $8)`,
msg.Chat.JID, msg.Chat.Receiver, msg.JID, msg.MXID, sender, msg.Timestamp.Unix(), msg.Sent) msg.Chat.JID, msg.Chat.Receiver, msg.JID, msg.MXID, sender, msg.Timestamp.Unix(), msg.Sent, msg.DecryptionError)
if err != nil { if err != nil {
msg.log.Warnfln("Failed to insert %s@%s: %v", msg.Chat, msg.JID, err) msg.log.Warnfln("Failed to insert %s@%s: %v", msg.Chat, msg.JID, err)
} }
@ -145,6 +162,15 @@ func (msg *Message) MarkSent() {
} }
} }
func (msg *Message) UpdateMXID(mxid id.EventID, stillDecryptionError bool) {
msg.MXID = mxid
msg.DecryptionError = stillDecryptionError
_, err := msg.db.Exec("UPDATE message SET mxid=$4, decryption_error=$5 WHERE chat_jid=$1 AND chat_receiver=$2 AND jid=$3", msg.Chat.JID, msg.Chat.Receiver, msg.JID, mxid, stillDecryptionError)
if err != nil {
msg.log.Warnfln("Failed to update %s@%s: %v", msg.Chat, msg.JID, err)
}
}
func (msg *Message) Delete() { func (msg *Message) Delete() {
_, err := msg.db.Exec("DELETE FROM message WHERE chat_jid=$1 AND chat_receiver=$2 AND jid=$3", msg.Chat.JID, msg.Chat.Receiver, msg.JID) _, err := msg.db.Exec("DELETE FROM message WHERE chat_jid=$1 AND chat_receiver=$2 AND jid=$3", msg.Chat.JID, msg.Chat.Receiver, msg.JID)
if err != nil { if err != nil {

View file

@ -0,0 +1,12 @@
package upgrades
import (
"database/sql"
)
func init() {
upgrades[27] = upgrade{"Add marker for WhatsApp decryption errors in message table", func(tx *sql.Tx, ctx context) error {
_, err := tx.Exec(`ALTER TABLE message ADD COLUMN decryption_error BOOLEAN NOT NULL DEFAULT false`)
return err
}}
}

View file

@ -39,7 +39,7 @@ type upgrade struct {
fn upgradeFunc fn upgradeFunc
} }
const NumberOfUpgrades = 27 const NumberOfUpgrades = 28
var upgrades [NumberOfUpgrades]upgrade var upgrades [NumberOfUpgrades]upgrade

142
portal.go
View file

@ -42,6 +42,8 @@ import (
"golang.org/x/image/webp" "golang.org/x/image/webp"
"google.golang.org/protobuf/proto" "google.golang.org/protobuf/proto"
"maunium.net/go/mautrix/format"
"go.mau.fi/whatsmeow" "go.mau.fi/whatsmeow"
waProto "go.mau.fi/whatsmeow/binary/proto" waProto "go.mau.fi/whatsmeow/binary/proto"
"go.mau.fi/whatsmeow/types" "go.mau.fi/whatsmeow/types"
@ -160,8 +162,14 @@ func (bridge *Bridge) NewPortal(dbPortal *database.Portal) *Portal {
const recentlyHandledLength = 100 const recentlyHandledLength = 100
type PortalMessage struct { type PortalMessage struct {
evt *events.Message evt *events.Message
source *User undecryptable *events.UndecryptableMessage
source *User
}
type recentlyHandledWrapper struct {
id types.MessageID
err bool
} }
type Portal struct { type Portal struct {
@ -174,7 +182,7 @@ type Portal struct {
encryptLock sync.Mutex encryptLock sync.Mutex
backfillLock sync.Mutex backfillLock sync.Mutex
recentlyHandled [recentlyHandledLength]types.MessageID recentlyHandled [recentlyHandledLength]recentlyHandledWrapper
recentlyHandledLock sync.Mutex recentlyHandledLock sync.Mutex
recentlyHandledIndex uint8 recentlyHandledIndex uint8
@ -185,8 +193,6 @@ type Portal struct {
hasRelaybot *bool hasRelaybot *bool
} }
const MaxMessageAgeToCreatePortal = 5 * 60 // 5 minutes
func (portal *Portal) syncDoublePuppetDetailsAfterCreate(source *User) { func (portal *Portal) syncDoublePuppetDetailsAfterCreate(source *User) {
doublePuppet := portal.bridge.GetPuppetByCustomMXID(source.MXID) doublePuppet := portal.bridge.GetPuppetByCustomMXID(source.MXID)
if doublePuppet == nil { if doublePuppet == nil {
@ -210,13 +216,20 @@ func (portal *Portal) handleMessageLoop() {
} }
portal.syncDoublePuppetDetailsAfterCreate(msg.source) portal.syncDoublePuppetDetailsAfterCreate(msg.source)
} }
//portal.backfillLock.Lock() if msg.evt != nil {
portal.handleMessage(msg.source, msg.evt) portal.handleMessage(msg.source, msg.evt)
//portal.backfillLock.Unlock() } else if msg.undecryptable != nil {
portal.handleUndecryptableMessage(msg.source, msg.undecryptable)
} else {
portal.log.Warnln("Unexpected PortalMessage with no message: %+v", msg)
}
} }
} }
func (portal *Portal) shouldCreateRoom(msg PortalMessage) bool { func (portal *Portal) shouldCreateRoom(msg PortalMessage) bool {
if msg.undecryptable != nil {
return true
}
waMsg := msg.evt.Message waMsg := msg.evt.Message
supportedMessages := []interface{}{ supportedMessages := []interface{}{
waMsg.Conversation, waMsg.Conversation,
@ -295,6 +308,30 @@ func (portal *Portal) convertMessage(intent *appservice.IntentAPI, source *User,
} }
} }
const UndecryptableMessage = "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))"
func (portal *Portal) handleUndecryptableMessage(source *User, evt *events.UndecryptableMessage) {
if len(portal.MXID) == 0 {
portal.log.Warnln("handleUndecryptableMessage called even though portal.MXID is empty")
return
} else if portal.isRecentlyHandled(evt.Info.ID, true) {
portal.log.Debugfln("Not handling %s (undecryptable): message was recently handled", evt.Info.ID)
return
} else if existingMsg := portal.bridge.DB.Message.GetByJID(portal.Key, evt.Info.ID); existingMsg != nil {
portal.log.Debugfln("Not handling %s (undecryptable): message is duplicate", evt.Info.ID)
return
}
intent := portal.getMessageIntent(source, &evt.Info)
content := format.RenderMarkdown(UndecryptableMessage, true, false)
content.MsgType = event.MsgNotice
resp, err := portal.sendMessage(intent, event.EventMessage, &content, evt.Info.Timestamp.UnixMilli())
if err != nil {
portal.log.Errorln("Failed to send decryption error of %s to Matrix: %v", evt.Info.ID, err)
}
portal.finishHandling(nil, &evt.Info, resp.EventID, true)
}
func (portal *Portal) handleMessage(source *User, evt *events.Message) { func (portal *Portal) handleMessage(source *User, evt *events.Message) {
if len(portal.MXID) == 0 { if len(portal.MXID) == 0 {
portal.log.Warnln("handleMessage called even though portal.MXID is empty") portal.log.Warnln("handleMessage called even though portal.MXID is empty")
@ -304,24 +341,35 @@ func (portal *Portal) handleMessage(source *User, evt *events.Message) {
msgType := portal.getMessageType(evt.Message) msgType := portal.getMessageType(evt.Message)
if msgType == "ignore" { if msgType == "ignore" {
return return
} else if portal.isRecentlyHandled(msgID) { } else if portal.isRecentlyHandled(msgID, false) {
portal.log.Debugfln("Not handling %s (%s): message was recently handled", msgID, msgType) portal.log.Debugfln("Not handling %s (%s): message was recently handled", msgID, msgType)
return return
} else if portal.isDuplicate(msgID) {
portal.log.Debugfln("Not handling %s (%s): message is duplicate", msgID, msgType)
return
} }
existingMsg := portal.bridge.DB.Message.GetByJID(portal.Key, msgID)
if existingMsg != nil {
if existingMsg.DecryptionError {
portal.log.Debugfln("Got decryptable version of previously undecryptable message %s (%s)", msgID, msgType)
} else {
portal.log.Debugfln("Not handling %s (%s): message is duplicate", msgID, msgType)
return
}
}
intent := portal.getMessageIntent(source, &evt.Info) intent := portal.getMessageIntent(source, &evt.Info)
converted := portal.convertMessage(intent, source, &evt.Info, evt.Message) converted := portal.convertMessage(intent, source, &evt.Info, evt.Message)
if converted != nil { if converted != nil {
var eventID id.EventID var eventID id.EventID
if existingMsg != nil {
converted.Content.SetEdit(existingMsg.MXID)
}
resp, err := portal.sendMessage(converted.Intent, converted.Type, converted.Content, evt.Info.Timestamp.UnixMilli()) resp, err := portal.sendMessage(converted.Intent, converted.Type, converted.Content, evt.Info.Timestamp.UnixMilli())
if err != nil { if err != nil {
portal.log.Errorln("Failed to send %s to Matrix: %v", msgID, err) portal.log.Errorln("Failed to send %s to Matrix: %v", msgID, err)
} else { } else {
eventID = resp.EventID eventID = resp.EventID
} }
if converted.Caption != nil { // TODO figure out how to handle captions with undecryptable messages turning decryptable
if converted.Caption != nil && existingMsg == nil {
resp, err = portal.sendMessage(converted.Intent, converted.Type, converted.Content, evt.Info.Timestamp.UnixMilli()) resp, err = portal.sendMessage(converted.Intent, converted.Type, converted.Content, evt.Info.Timestamp.UnixMilli())
if err != nil { if err != nil {
portal.log.Errorln("Failed to send caption of %s to Matrix: %v", msgID, err) portal.log.Errorln("Failed to send caption of %s to Matrix: %v", msgID, err)
@ -330,55 +378,65 @@ func (portal *Portal) handleMessage(source *User, evt *events.Message) {
} }
} }
if len(eventID) != 0 { if len(eventID) != 0 {
portal.finishHandling(&evt.Info, resp.EventID) portal.finishHandling(existingMsg, &evt.Info, resp.EventID, false)
} }
} else if msgType == "revoke" { } else if msgType == "revoke" {
portal.HandleMessageRevoke(source, evt.Message.GetProtocolMessage().GetKey()) portal.HandleMessageRevoke(source, evt.Message.GetProtocolMessage().GetKey())
if existingMsg != nil {
_, _ = portal.MainIntent().RedactEvent(portal.MXID, existingMsg.MXID, mautrix.ReqRedact{
Reason: "The undecryptable message was actually the deletion of another message",
})
existingMsg.UpdateMXID("net.maunium.whatsapp.fake::" + existingMsg.MXID, false)
}
} else { } else {
portal.log.Warnln("Unhandled message:", evt.Info, evt.Message) portal.log.Warnln("Unhandled message:", evt.Info, evt.Message)
if existingMsg != nil {
_, _ = portal.MainIntent().RedactEvent(portal.MXID, existingMsg.MXID, mautrix.ReqRedact{
Reason: "The undecryptable message contained an unsupported message type",
})
existingMsg.UpdateMXID("net.maunium.whatsapp.fake::" + existingMsg.MXID, false)
}
return return
} }
portal.bridge.Metrics.TrackWhatsAppMessage(evt.Info.Timestamp, strings.Split(msgType, " ")[0]) portal.bridge.Metrics.TrackWhatsAppMessage(evt.Info.Timestamp, strings.Split(msgType, " ")[0])
} }
func (portal *Portal) isRecentlyHandled(id types.MessageID) bool { func (portal *Portal) isRecentlyHandled(id types.MessageID, decryptionError bool) bool {
start := portal.recentlyHandledIndex start := portal.recentlyHandledIndex
lookingForMsg := recentlyHandledWrapper{id, decryptionError}
for i := start; i != start; i = (i - 1) % recentlyHandledLength { for i := start; i != start; i = (i - 1) % recentlyHandledLength {
if portal.recentlyHandled[i] == id { if portal.recentlyHandled[i] == lookingForMsg {
return true return true
} }
} }
return false return false
} }
func (portal *Portal) isDuplicate(id types.MessageID) bool {
msg := portal.bridge.DB.Message.GetByJID(portal.Key, id)
if msg != nil {
return true
}
return false
}
func init() { func init() {
gob.Register(&waProto.Message{}) gob.Register(&waProto.Message{})
} }
func (portal *Portal) markHandled(info *types.MessageInfo, mxid id.EventID, isSent, recent bool) *database.Message { func (portal *Portal) markHandled(msg *database.Message, info *types.MessageInfo, mxid id.EventID, isSent, recent, decryptionError bool) *database.Message {
msg := portal.bridge.DB.Message.New() if msg == nil {
msg.Chat = portal.Key msg = portal.bridge.DB.Message.New()
msg.JID = info.ID msg.Chat = portal.Key
msg.MXID = mxid msg.JID = info.ID
msg.Timestamp = info.Timestamp msg.MXID = mxid
msg.Sender = info.Sender msg.Timestamp = info.Timestamp
msg.Sent = isSent msg.Sender = info.Sender
msg.Insert() msg.Sent = isSent
msg.DecryptionError = decryptionError
msg.Insert()
} else {
msg.UpdateMXID(mxid, decryptionError)
}
if recent { if recent {
portal.recentlyHandledLock.Lock() portal.recentlyHandledLock.Lock()
index := portal.recentlyHandledIndex index := portal.recentlyHandledIndex
portal.recentlyHandledIndex = (portal.recentlyHandledIndex + 1) % recentlyHandledLength portal.recentlyHandledIndex = (portal.recentlyHandledIndex + 1) % recentlyHandledLength
portal.recentlyHandledLock.Unlock() portal.recentlyHandledLock.Unlock()
portal.recentlyHandled[index] = msg.JID portal.recentlyHandled[index] = recentlyHandledWrapper{msg.JID, decryptionError}
} }
return msg return msg
} }
@ -406,10 +464,14 @@ func (portal *Portal) getMessageIntent(user *User, info *types.MessageInfo) *app
return puppet.IntentFor(portal) return puppet.IntentFor(portal)
} }
func (portal *Portal) finishHandling(message *types.MessageInfo, mxid id.EventID) { func (portal *Portal) finishHandling(existing *database.Message, message *types.MessageInfo, mxid id.EventID, decryptionError bool) {
portal.markHandled(message, mxid, true, true) portal.markHandled(existing, message, mxid, true, true, decryptionError)
portal.sendDeliveryReceipt(mxid) portal.sendDeliveryReceipt(mxid)
portal.log.Debugln("Handled message", message.ID, "->", mxid) if !decryptionError {
portal.log.Debugln("Handled message", message.ID, "->", mxid)
} else {
portal.log.Debugln("Handled message", message.ID, "->", mxid, "(undecryptable message error notice)")
}
} }
func (portal *Portal) kickExtraUsers(participantMap map[types.JID]bool) { func (portal *Portal) kickExtraUsers(participantMap map[types.JID]bool) {
@ -896,13 +958,13 @@ func (portal *Portal) finishBatch(eventIDs []id.EventID, infos []*types.MessageI
} else if info, ok := infoMap[types.MessageID(msgID)]; !ok { } else if info, ok := infoMap[types.MessageID(msgID)]; !ok {
portal.log.Warnfln("Didn't find info of message %s (event %s) to register it in the database", msgID, eventID) portal.log.Warnfln("Didn't find info of message %s (event %s) to register it in the database", msgID, eventID)
} else { } else {
portal.markHandled(info, eventID, true, false) portal.markHandled(nil, info, eventID, true, false, false)
} }
} }
} else { } else {
for i := 0; i < len(infos); i++ { for i := 0; i < len(infos); i++ {
if infos[i] != nil { if infos[i] != nil {
portal.markHandled(infos[i], eventIDs[i], true, false) portal.markHandled(nil, infos[i], eventIDs[i], true, false, false)
} }
} }
portal.log.Infofln("Successfully sent %d events", len(eventIDs)) portal.log.Infofln("Successfully sent %d events", len(eventIDs))
@ -2358,7 +2420,7 @@ func (portal *Portal) HandleMatrixMessage(sender *User, evt *event.Event) {
return return
} }
info := portal.generateMessageInfo(sender) info := portal.generateMessageInfo(sender)
dbMsg := portal.markHandled(info, evt.ID, false, true) dbMsg := portal.markHandled(nil, info, evt.ID, false, true, false)
portal.log.Debugln("Sending event", evt.ID, "to WhatsApp", info.ID) portal.log.Debugln("Sending event", evt.ID, "to WhatsApp", info.ID)
err := sender.Client.SendMessage(portal.Key.JID, info.ID, msg) err := sender.Client.SendMessage(portal.Key.JID, info.ID, msg)
if err != nil { if err != nil {

View file

@ -436,7 +436,10 @@ func (user *User) HandleEvent(event interface{}) {
go user.handleReceipt(v) go user.handleReceipt(v)
case *events.Message: case *events.Message:
portal := user.GetPortalByJID(v.Info.Chat) portal := user.GetPortalByJID(v.Info.Chat)
portal.messages <- PortalMessage{v, user} portal.messages <- PortalMessage{evt: v, source: user}
case *events.UndecryptableMessage:
portal := user.GetPortalByJID(v.Info.Chat)
portal.messages <- PortalMessage{undecryptable: v, source: user}
case *events.HistorySync: case *events.HistorySync:
user.historySyncs <- v user.historySyncs <- v
case *events.Mute: case *events.Mute:
@ -458,6 +461,8 @@ func (user *User) HandleEvent(event interface{}) {
if portal != nil { if portal != nil {
go user.updateChatTag(nil, portal, user.bridge.Config.Bridge.PinnedTag, v.Action.GetPinned()) go user.updateChatTag(nil, portal, user.bridge.Config.Bridge.PinnedTag, v.Action.GetPinned())
} }
case *events.AppState:
// Ignore
default: default:
user.log.Debugfln("Unknown type of event in HandleEvent: %T", v) user.log.Debugfln("Unknown type of event in HandleEvent: %T", v)
} }