forked from MirrorHub/mautrix-whatsapp
Implement WhatsApp->Matrix group info updates
This commit is contained in:
parent
1ad17048cc
commit
149e9bc8af
6 changed files with 280 additions and 324 deletions
|
@ -42,15 +42,13 @@ type BridgeConfig struct {
|
|||
End bool `yaml:"end"`
|
||||
} `yaml:"call_notices"`
|
||||
|
||||
InitialChatSync int `yaml:"initial_chat_sync_count"`
|
||||
InitialHistoryFill int `yaml:"initial_history_fill_count"`
|
||||
HistoryDisableNotifs bool `yaml:"initial_history_disable_notifications"`
|
||||
RecoverChatSync int `yaml:"recovery_chat_sync_count"`
|
||||
RecoverHistory bool `yaml:"recovery_history_backfill"`
|
||||
ChatMetaSync bool `yaml:"chat_meta_sync"`
|
||||
UserAvatarSync bool `yaml:"user_avatar_sync"`
|
||||
BridgeMatrixLeave bool `yaml:"bridge_matrix_leave"`
|
||||
SyncChatMaxAge int64 `yaml:"sync_max_chat_age"`
|
||||
HistorySync struct {
|
||||
CreatePortals bool `yaml:"create_portals"`
|
||||
Backfill bool `yaml:"backfill"`
|
||||
DoublePuppetBackfill bool `yaml:"double_puppet_backfill"`
|
||||
}
|
||||
UserAvatarSync bool `yaml:"user_avatar_sync"`
|
||||
BridgeMatrixLeave bool `yaml:"bridge_matrix_leave"`
|
||||
|
||||
SyncWithCustomPuppets bool `yaml:"sync_with_custom_puppets"`
|
||||
SyncDirectChatList bool `yaml:"sync_direct_chat_list"`
|
||||
|
@ -58,7 +56,6 @@ type BridgeConfig struct {
|
|||
DefaultBridgePresence bool `yaml:"default_bridge_presence"`
|
||||
LoginSharedSecret string `yaml:"login_shared_secret"`
|
||||
|
||||
DoublePuppetBackfill bool `yaml:"double_puppet_backfill"`
|
||||
PrivateChatPortalMeta bool `yaml:"private_chat_portal_meta"`
|
||||
BridgeNotices bool `yaml:"bridge_notices"`
|
||||
ResendBridgeInfo bool `yaml:"resend_bridge_info"`
|
||||
|
@ -95,7 +92,6 @@ type BridgeConfig struct {
|
|||
}
|
||||
|
||||
func (bc *BridgeConfig) setDefaults() {
|
||||
bc.DeliveryReceipts = false
|
||||
bc.MaxConnectionAttempts = 3
|
||||
bc.ConnectionRetryDelay = -1
|
||||
bc.ReportConnectionRetry = true
|
||||
|
@ -104,22 +100,14 @@ func (bc *BridgeConfig) setDefaults() {
|
|||
bc.CallNotices.Start = true
|
||||
bc.CallNotices.End = true
|
||||
|
||||
bc.InitialChatSync = 10
|
||||
bc.InitialHistoryFill = 20
|
||||
bc.RecoverChatSync = -1
|
||||
bc.RecoverHistory = true
|
||||
bc.ChatMetaSync = true
|
||||
bc.HistorySync.CreatePortals = true
|
||||
bc.UserAvatarSync = true
|
||||
bc.BridgeMatrixLeave = true
|
||||
bc.SyncChatMaxAge = 259200
|
||||
|
||||
bc.SyncWithCustomPuppets = true
|
||||
bc.DefaultBridgePresence = true
|
||||
bc.DefaultBridgeReceipts = true
|
||||
bc.LoginSharedSecret = ""
|
||||
|
||||
bc.DoublePuppetBackfill = false
|
||||
bc.PrivateChatPortalMeta = false
|
||||
bc.BridgeNotices = true
|
||||
bc.EnableStatusBroadcast = true
|
||||
}
|
||||
|
|
|
@ -101,32 +101,20 @@ bridge:
|
|||
start: true
|
||||
end: true
|
||||
|
||||
# Number of chats to sync for new users.
|
||||
initial_chat_sync_count: 10
|
||||
# Number of old messages to fill when creating new portal rooms.
|
||||
initial_history_fill_count: 20
|
||||
# Whether or not notifications should be turned off while filling initial history.
|
||||
# Only applicable when using double puppeting.
|
||||
initial_history_disable_notifications: false
|
||||
# Maximum number of chats to sync when recovering from downtime.
|
||||
# Set to -1 to sync all new chats during downtime.
|
||||
recovery_chat_sync_limit: -1
|
||||
# Whether or not to sync history when recovering from downtime.
|
||||
recovery_history_backfill: true
|
||||
# Whether or not portal info should be fetched from the server when syncing,
|
||||
# instead of relying on finding any changes in the message history.
|
||||
# If you get 599 errors often, you should try disabling this.
|
||||
chat_meta_sync: true
|
||||
history_sync:
|
||||
# Whether to create portals from history sync payloads from WhatsApp.
|
||||
create_portals: true
|
||||
# Whether to enable backfilling history sync payloads from WhatsApp using batch sending
|
||||
# This requires a server with MSC2716 support, which is currently an experimental feature in synapse.
|
||||
# It can be enabled by setting experimental_features -> enable_msc2716 to true in homeserver.yaml.
|
||||
backfill: false
|
||||
# Whether to use custom puppet for backfilling.
|
||||
# In order to use this, the custom puppets must be in the appservice's user ID namespace.
|
||||
double_puppet_backfill: false
|
||||
# Whether or not puppet avatars should be fetched from the server even if an avatar is already set.
|
||||
# If you get 599 errors often, you should try disabling this.
|
||||
user_avatar_sync: true
|
||||
# Whether or not Matrix users leaving groups should be bridged to WhatsApp
|
||||
bridge_matrix_leave: true
|
||||
# Maximum number of seconds since last message in chat to skip
|
||||
# syncing the chat in any case. This setting will take priority
|
||||
# over both recovery_chat_sync_limit and initial_chat_sync_count.
|
||||
# Default is 3 days = 259200 seconds
|
||||
sync_max_chat_age: 259200
|
||||
|
||||
# Whether or not to sync with custom puppets to receive EDUs that
|
||||
# are not normally sent to appservices.
|
||||
|
@ -147,18 +135,12 @@ bridge:
|
|||
# manually.
|
||||
login_shared_secret: null
|
||||
|
||||
# Whether to use custom puppet for backfilling.
|
||||
# In order to use this, the custom puppets must be in the appservice's user ID namespace.
|
||||
double_puppet_backfill: false
|
||||
# Whether or not to explicitly set the avatar and room name for private
|
||||
# chat portal rooms. This can be useful if the previous field works fine,
|
||||
# but causes room avatar/name bugs.
|
||||
# Whether to explicitly set the avatar and room name for private chat portal rooms.
|
||||
private_chat_portal_meta: false
|
||||
# Whether or not Matrix m.notice-type messages should be bridged.
|
||||
# Whether Matrix m.notice-type messages should be bridged.
|
||||
bridge_notices: true
|
||||
# Set this to true to tell the bridge to re-send m.bridge events to all rooms on the next run.
|
||||
# This field will automatically be changed back to false after it,
|
||||
# except if the config file is not writable.
|
||||
# This field will automatically be changed back to false after it, except if the config file is not writable.
|
||||
resend_bridge_info: false
|
||||
# When using double puppeting, should muted chats be muted in Matrix?
|
||||
mute_bridging: false
|
||||
|
|
2
go.mod
2
go.mod
|
@ -8,7 +8,7 @@ require (
|
|||
github.com/mattn/go-sqlite3 v1.14.9
|
||||
github.com/prometheus/client_golang v1.11.0
|
||||
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e
|
||||
go.mau.fi/whatsmeow v0.0.0-20211027183133-07bcb11ceb48
|
||||
go.mau.fi/whatsmeow v0.0.0-20211028095847-2a72655ef600
|
||||
golang.org/x/image v0.0.0-20210628002857-a66eb6448b8d
|
||||
google.golang.org/protobuf v1.27.1
|
||||
gopkg.in/yaml.v2 v2.4.0
|
||||
|
|
4
go.sum
4
go.sum
|
@ -139,8 +139,8 @@ github.com/tidwall/sjson v1.2.3 h1:5+deguEhHSEjmuICXZ21uSSsXotWMA0orU783+Z7Cp8=
|
|||
github.com/tidwall/sjson v1.2.3/go.mod h1:5WdjKx3AQMvCJ4RG6/2UYT7dLrGvJUV1x4jdTAyGvZs=
|
||||
go.mau.fi/libsignal v0.0.0-20211024113310-f9fc6a1855f2 h1:xpQTMgJGGaF+c8jV/LA/FVXAPJxZbSAGeflOc+Ly6uQ=
|
||||
go.mau.fi/libsignal v0.0.0-20211024113310-f9fc6a1855f2/go.mod h1:3XlVlwOfp8f9Wri+C1D4ORqgUsN4ZvunJOoPjQMBhos=
|
||||
go.mau.fi/whatsmeow v0.0.0-20211027183133-07bcb11ceb48 h1:e4cAP66APziJd8YFAJbYtPtkMJLi4wullnqs87lWZWo=
|
||||
go.mau.fi/whatsmeow v0.0.0-20211027183133-07bcb11ceb48/go.mod h1:ODEmmqeUn9eBDQHFc1S902YA3YFLtmaBujYRRFl53jI=
|
||||
go.mau.fi/whatsmeow v0.0.0-20211028095847-2a72655ef600 h1:3huw0OOUNmU1c9vJHifEdTJJnFn6UchoHFaazdHkd34=
|
||||
go.mau.fi/whatsmeow v0.0.0-20211028095847-2a72655ef600/go.mod h1:ODEmmqeUn9eBDQHFc1S902YA3YFLtmaBujYRRFl53jI=
|
||||
golang.org/x/crypto v0.0.0-20170930174604-9419663f5a44/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
|
|
313
portal.go
313
portal.go
|
@ -193,14 +193,6 @@ type Portal struct {
|
|||
hasRelaybot *bool
|
||||
}
|
||||
|
||||
func (portal *Portal) syncDoublePuppetDetailsAfterCreate(source *User) {
|
||||
doublePuppet := portal.bridge.GetPuppetByCustomMXID(source.MXID)
|
||||
if doublePuppet == nil {
|
||||
return
|
||||
}
|
||||
source.syncChatDoublePuppetDetails(doublePuppet, portal, true)
|
||||
}
|
||||
|
||||
func (portal *Portal) handleMessageLoop() {
|
||||
for msg := range portal.messages {
|
||||
if len(portal.MXID) == 0 {
|
||||
|
@ -214,7 +206,6 @@ func (portal *Portal) handleMessageLoop() {
|
|||
portal.log.Errorln("Failed to create portal room:", err)
|
||||
continue
|
||||
}
|
||||
portal.syncDoublePuppetDetailsAfterCreate(msg.source)
|
||||
}
|
||||
if msg.evt != nil {
|
||||
portal.handleMessage(msg.source, msg.evt)
|
||||
|
@ -310,6 +301,7 @@ func (portal *Portal) convertMessage(intent *appservice.IntentAPI, source *User,
|
|||
|
||||
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() {
|
||||
|
@ -391,7 +383,7 @@ func (portal *Portal) handleMessage(source *User, evt *events.Message) {
|
|||
_, _ = 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)
|
||||
existingMsg.UpdateMXID("net.maunium.whatsapp.fake::"+existingMsg.MXID, false)
|
||||
}
|
||||
} else {
|
||||
portal.log.Warnln("Unhandled message:", evt.Info, evt.Message)
|
||||
|
@ -399,7 +391,7 @@ func (portal *Portal) handleMessage(source *User, evt *events.Message) {
|
|||
_, _ = 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)
|
||||
existingMsg.UpdateMXID("net.maunium.whatsapp.fake::"+existingMsg.MXID, false)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
@ -557,7 +549,7 @@ func (portal *Portal) SyncParticipants(source *User, metadata *types.GroupInfo)
|
|||
portal.kickExtraUsers(participantMap)
|
||||
}
|
||||
|
||||
func (portal *Portal) UpdateAvatar(user *User, updateInfo bool) bool {
|
||||
func (portal *Portal) UpdateAvatar(user *User, setBy types.JID, updateInfo bool) bool {
|
||||
avatar, err := user.Client.GetProfilePictureInfo(portal.Key.JID, false)
|
||||
if err != nil {
|
||||
if !errors.Is(err, whatsmeow.ErrProfilePictureUnauthorized) {
|
||||
|
@ -585,7 +577,14 @@ func (portal *Portal) UpdateAvatar(user *User, updateInfo bool) bool {
|
|||
}
|
||||
|
||||
if len(portal.MXID) > 0 {
|
||||
_, err := portal.MainIntent().SetRoomAvatar(portal.MXID, portal.AvatarURL)
|
||||
intent := portal.MainIntent()
|
||||
if !setBy.IsEmpty() {
|
||||
intent = portal.bridge.GetPuppetByJID(setBy).IntentFor(portal)
|
||||
}
|
||||
_, err = intent.SetRoomAvatar(portal.MXID, portal.AvatarURL)
|
||||
if errors.Is(err, mautrix.MForbidden) && intent != portal.MainIntent() {
|
||||
_, err = portal.MainIntent().SetRoomAvatar(portal.MXID, portal.AvatarURL)
|
||||
}
|
||||
if err != nil {
|
||||
portal.log.Warnln("Failed to set room topic:", err)
|
||||
return false
|
||||
|
@ -598,20 +597,22 @@ func (portal *Portal) UpdateAvatar(user *User, updateInfo bool) bool {
|
|||
return true
|
||||
}
|
||||
|
||||
func (portal *Portal) UpdateName(name string, setBy types.JID, intent *appservice.IntentAPI, updateInfo bool) bool {
|
||||
func (portal *Portal) UpdateName(name string, setBy types.JID, updateInfo bool) bool {
|
||||
if name == "" && portal.IsBroadcastList() {
|
||||
name = UnnamedBroadcastName
|
||||
}
|
||||
if portal.Name != name {
|
||||
portal.log.Debugfln("Updating name %s -> %s", portal.Name, name)
|
||||
portal.Name = name
|
||||
if intent == nil {
|
||||
intent = portal.MainIntent()
|
||||
if !setBy.IsEmpty() {
|
||||
intent = portal.bridge.GetPuppetByJID(setBy).IntentFor(portal)
|
||||
}
|
||||
|
||||
intent := portal.MainIntent()
|
||||
if !setBy.IsEmpty() {
|
||||
intent = portal.bridge.GetPuppetByJID(setBy).IntentFor(portal)
|
||||
}
|
||||
_, err := intent.SetRoomName(portal.MXID, name)
|
||||
if errors.Is(err, mautrix.MForbidden) && intent != portal.MainIntent() {
|
||||
_, err = portal.MainIntent().SetRoomName(portal.MXID, name)
|
||||
}
|
||||
if err == nil {
|
||||
if updateInfo {
|
||||
portal.UpdateBridgeInfo()
|
||||
|
@ -625,17 +626,19 @@ func (portal *Portal) UpdateName(name string, setBy types.JID, intent *appservic
|
|||
return false
|
||||
}
|
||||
|
||||
func (portal *Portal) UpdateTopic(topic string, setBy types.JID, intent *appservice.IntentAPI, updateInfo bool) bool {
|
||||
func (portal *Portal) UpdateTopic(topic string, setBy types.JID, updateInfo bool) bool {
|
||||
if portal.Topic != topic {
|
||||
portal.log.Debugfln("Updating topic %s -> %s", portal.Topic, topic)
|
||||
portal.Topic = topic
|
||||
if intent == nil {
|
||||
intent = portal.MainIntent()
|
||||
if !setBy.IsEmpty() {
|
||||
intent = portal.bridge.GetPuppetByJID(setBy).IntentFor(portal)
|
||||
}
|
||||
|
||||
intent := portal.MainIntent()
|
||||
if !setBy.IsEmpty() {
|
||||
intent = portal.bridge.GetPuppetByJID(setBy).IntentFor(portal)
|
||||
}
|
||||
_, err := intent.SetRoomTopic(portal.MXID, topic)
|
||||
if errors.Is(err, mautrix.MForbidden) && intent != portal.MainIntent() {
|
||||
_, err = portal.MainIntent().SetRoomTopic(portal.MXID, topic)
|
||||
}
|
||||
if err == nil {
|
||||
if updateInfo {
|
||||
portal.UpdateBridgeInfo()
|
||||
|
@ -654,8 +657,8 @@ func (portal *Portal) UpdateMetadata(user *User) bool {
|
|||
return false
|
||||
} else if portal.IsStatusBroadcastList() {
|
||||
update := false
|
||||
update = portal.UpdateName(StatusBroadcastName, types.EmptyJID, nil, false) || update
|
||||
update = portal.UpdateTopic(StatusBroadcastTopic, types.EmptyJID, nil, false) || update
|
||||
update = portal.UpdateName(StatusBroadcastName, types.EmptyJID, false) || update
|
||||
update = portal.UpdateTopic(StatusBroadcastTopic, types.EmptyJID, false) || update
|
||||
return update
|
||||
} else if portal.IsBroadcastList() {
|
||||
update := false
|
||||
|
@ -680,8 +683,8 @@ func (portal *Portal) UpdateMetadata(user *User) bool {
|
|||
|
||||
portal.SyncParticipants(user, metadata)
|
||||
update := false
|
||||
update = portal.UpdateName(metadata.Name, metadata.NameSetBy, nil, false) || update
|
||||
update = portal.UpdateTopic(metadata.Topic, metadata.TopicSetBy, nil, false) || update
|
||||
update = portal.UpdateName(metadata.Name, metadata.NameSetBy, false) || update
|
||||
update = portal.UpdateTopic(metadata.Topic, metadata.TopicSetBy, false) || update
|
||||
|
||||
portal.RestrictMessageSending(metadata.IsAnnounce)
|
||||
portal.RestrictMetadataChanges(metadata.IsLocked)
|
||||
|
@ -764,7 +767,7 @@ func (portal *Portal) Sync(user *User) bool {
|
|||
update := false
|
||||
update = portal.UpdateMetadata(user) || update
|
||||
if !portal.IsPrivateChat() && !portal.IsBroadcastList() && portal.Avatar == "" {
|
||||
update = portal.UpdateAvatar(user, false) || update
|
||||
update = portal.UpdateAvatar(user, types.EmptyJID, false) || update
|
||||
}
|
||||
if update {
|
||||
portal.Update()
|
||||
|
@ -798,35 +801,35 @@ func (portal *Portal) GetBasePowerLevels() *event.PowerLevelsEventContent {
|
|||
}
|
||||
}
|
||||
|
||||
//func (portal *Portal) ChangeAdminStatus(jids []string, setAdmin bool) id.EventID {
|
||||
// levels, err := portal.MainIntent().PowerLevels(portal.MXID)
|
||||
// if err != nil {
|
||||
// levels = portal.GetBasePowerLevels()
|
||||
// }
|
||||
// newLevel := 0
|
||||
// if setAdmin {
|
||||
// newLevel = 50
|
||||
// }
|
||||
// changed := false
|
||||
// for _, jid := range jids {
|
||||
// puppet := portal.bridge.GetPuppetByJID(jid)
|
||||
// changed = levels.EnsureUserLevel(puppet.MXID, newLevel) || changed
|
||||
//
|
||||
// user := portal.bridge.GetUserByJID(jid)
|
||||
// if user != nil {
|
||||
// changed = levels.EnsureUserLevel(user.MXID, newLevel) || changed
|
||||
// }
|
||||
// }
|
||||
// if changed {
|
||||
// resp, err := portal.MainIntent().SetPowerLevels(portal.MXID, levels)
|
||||
// if err != nil {
|
||||
// portal.log.Errorln("Failed to change power levels:", err)
|
||||
// } else {
|
||||
// return resp.EventID
|
||||
// }
|
||||
// }
|
||||
// return ""
|
||||
//}
|
||||
func (portal *Portal) ChangeAdminStatus(jids []types.JID, setAdmin bool) id.EventID {
|
||||
levels, err := portal.MainIntent().PowerLevels(portal.MXID)
|
||||
if err != nil {
|
||||
levels = portal.GetBasePowerLevels()
|
||||
}
|
||||
newLevel := 0
|
||||
if setAdmin {
|
||||
newLevel = 50
|
||||
}
|
||||
changed := false
|
||||
for _, jid := range jids {
|
||||
puppet := portal.bridge.GetPuppetByJID(jid)
|
||||
changed = levels.EnsureUserLevel(puppet.MXID, newLevel) || changed
|
||||
|
||||
user := portal.bridge.GetUserByJID(jid)
|
||||
if user != nil {
|
||||
changed = levels.EnsureUserLevel(user.MXID, newLevel) || changed
|
||||
}
|
||||
}
|
||||
if changed {
|
||||
resp, err := portal.MainIntent().SetPowerLevels(portal.MXID, levels)
|
||||
if err != nil {
|
||||
portal.log.Errorln("Failed to change power levels:", err)
|
||||
} else {
|
||||
return resp.EventID
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (portal *Portal) RestrictMessageSending(restrict bool) id.EventID {
|
||||
levels, err := portal.MainIntent().PowerLevels(portal.MXID)
|
||||
|
@ -1079,7 +1082,7 @@ func (portal *Portal) backfill(source *User, messages []*waProto.HistorySyncMsg)
|
|||
intent = puppet.DefaultIntent()
|
||||
} else {
|
||||
intent = puppet.IntentFor(portal)
|
||||
if intent.IsCustomPuppet && !portal.bridge.Config.Bridge.DoublePuppetBackfill {
|
||||
if intent.IsCustomPuppet && !portal.bridge.Config.Bridge.HistorySync.DoublePuppetBackfill {
|
||||
intent = puppet.DefaultIntent()
|
||||
addMember(puppet)
|
||||
}
|
||||
|
@ -1266,7 +1269,7 @@ func (portal *Portal) CreateMatrixRoom(user *User) error {
|
|||
portal.Name = metadata.Name
|
||||
portal.Topic = metadata.Topic
|
||||
}
|
||||
portal.UpdateAvatar(user, false)
|
||||
portal.UpdateAvatar(user, types.EmptyJID, false)
|
||||
}
|
||||
|
||||
bridgeInfoStateKey, bridgeInfo := portal.getBridgeInfo()
|
||||
|
@ -1337,6 +1340,7 @@ func (portal *Portal) CreateMatrixRoom(user *User) error {
|
|||
}
|
||||
|
||||
portal.ensureUserInvited(user)
|
||||
user.syncChatDoublePuppetDetails(portal, true)
|
||||
|
||||
if metadata != nil {
|
||||
portal.SyncParticipants(user, metadata)
|
||||
|
@ -1678,95 +1682,104 @@ func (portal *Portal) convertContactMessage(intent *appservice.IntentAPI, msg *w
|
|||
return &ConvertedMessage{Intent: intent, Type: event.EventMessage, Content: content}
|
||||
}
|
||||
|
||||
// FIXME
|
||||
//func (portal *Portal) tryKickUser(userID id.UserID, intent *appservice.IntentAPI) error {
|
||||
// _, err := intent.KickUser(portal.MXID, &mautrix.ReqKickUser{UserID: userID})
|
||||
// if err != nil {
|
||||
// httpErr, ok := err.(mautrix.HTTPError)
|
||||
// if ok && httpErr.RespError != nil && httpErr.RespError.ErrCode == "M_FORBIDDEN" {
|
||||
// _, err = portal.MainIntent().KickUser(portal.MXID, &mautrix.ReqKickUser{UserID: userID})
|
||||
// }
|
||||
// }
|
||||
// return err
|
||||
//}
|
||||
//
|
||||
//func (portal *Portal) removeUser(isSameUser bool, kicker *appservice.IntentAPI, target id.UserID, targetIntent *appservice.IntentAPI) {
|
||||
// if !isSameUser || targetIntent == nil {
|
||||
// err := portal.tryKickUser(target, kicker)
|
||||
// if err != nil {
|
||||
// portal.log.Warnfln("Failed to kick %s from %s: %v", target, portal.MXID, err)
|
||||
// if targetIntent != nil {
|
||||
// _, _ = targetIntent.LeaveRoom(portal.MXID)
|
||||
// }
|
||||
// }
|
||||
// } else {
|
||||
// _, err := targetIntent.LeaveRoom(portal.MXID)
|
||||
// if err != nil {
|
||||
// portal.log.Warnfln("Failed to leave portal as %s: %v", target, err)
|
||||
// _, _ = portal.MainIntent().KickUser(portal.MXID, &mautrix.ReqKickUser{UserID: target})
|
||||
// }
|
||||
// }
|
||||
//}
|
||||
//
|
||||
//func (portal *Portal) HandleWhatsAppKick(source *User, senderJID string, jids []string) {
|
||||
// sender := portal.bridge.GetPuppetByJID(senderJID)
|
||||
// senderIntent := sender.IntentFor(portal)
|
||||
// for _, jid := range jids {
|
||||
// if source != nil && source.JID == jid {
|
||||
// portal.log.Debugln("Ignoring self-kick by", source.MXID)
|
||||
// continue
|
||||
// }
|
||||
// puppet := portal.bridge.GetPuppetByJID(jid)
|
||||
// portal.removeUser(puppet.JID == sender.JID, senderIntent, puppet.MXID, puppet.DefaultIntent())
|
||||
//
|
||||
// if !portal.IsBroadcastList() {
|
||||
// user := portal.bridge.GetUserByJID(jid)
|
||||
// if user != nil {
|
||||
// var customIntent *appservice.IntentAPI
|
||||
// if puppet.CustomMXID == user.MXID {
|
||||
// customIntent = puppet.CustomIntent()
|
||||
// }
|
||||
// portal.removeUser(puppet.JID == sender.JID, senderIntent, user.MXID, customIntent)
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
//}
|
||||
//
|
||||
//func (portal *Portal) HandleWhatsAppInvite(source *User, senderJID string, intent *appservice.IntentAPI, jids []string) (evtID id.EventID) {
|
||||
// if intent == nil {
|
||||
// intent = portal.MainIntent()
|
||||
// if senderJID != "unknown" {
|
||||
// sender := portal.bridge.GetPuppetByJID(senderJID)
|
||||
// intent = sender.IntentFor(portal)
|
||||
// }
|
||||
// }
|
||||
// for _, jid := range jids {
|
||||
// puppet := portal.bridge.GetPuppetByJID(jid)
|
||||
// puppet.SyncContact(source, true)
|
||||
// content := event.Content{
|
||||
// Parsed: event.MemberEventContent{
|
||||
// Membership: "invite",
|
||||
// Displayname: puppet.Displayname,
|
||||
// AvatarURL: puppet.AvatarURL.CUString(),
|
||||
// },
|
||||
// Raw: map[string]interface{}{
|
||||
// "net.maunium.whatsapp.puppet": true,
|
||||
// },
|
||||
// }
|
||||
// resp, err := intent.SendStateEvent(portal.MXID, event.StateMember, puppet.MXID.String(), &content)
|
||||
// if err != nil {
|
||||
// portal.log.Warnfln("Failed to invite %s as %s: %v", puppet.MXID, intent.UserID, err)
|
||||
// _ = portal.MainIntent().EnsureInvited(portal.MXID, puppet.MXID)
|
||||
// } else {
|
||||
// evtID = resp.EventID
|
||||
// }
|
||||
// err = puppet.DefaultIntent().EnsureJoined(portal.MXID)
|
||||
// if err != nil {
|
||||
// portal.log.Errorfln("Failed to ensure %s is joined: %v", puppet.MXID, err)
|
||||
// }
|
||||
// }
|
||||
// return
|
||||
//}
|
||||
func (portal *Portal) tryKickUser(userID id.UserID, intent *appservice.IntentAPI) error {
|
||||
_, err := intent.KickUser(portal.MXID, &mautrix.ReqKickUser{UserID: userID})
|
||||
if err != nil {
|
||||
httpErr, ok := err.(mautrix.HTTPError)
|
||||
if ok && httpErr.RespError != nil && httpErr.RespError.ErrCode == "M_FORBIDDEN" {
|
||||
_, err = portal.MainIntent().KickUser(portal.MXID, &mautrix.ReqKickUser{UserID: userID})
|
||||
}
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func (portal *Portal) removeUser(isSameUser bool, kicker *appservice.IntentAPI, target id.UserID, targetIntent *appservice.IntentAPI) {
|
||||
if !isSameUser || targetIntent == nil {
|
||||
err := portal.tryKickUser(target, kicker)
|
||||
if err != nil {
|
||||
portal.log.Warnfln("Failed to kick %s from %s: %v", target, portal.MXID, err)
|
||||
if targetIntent != nil {
|
||||
_, _ = portal.leaveWithPuppetMeta(targetIntent)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
_, err := portal.leaveWithPuppetMeta(targetIntent)
|
||||
if err != nil {
|
||||
portal.log.Warnfln("Failed to leave portal as %s: %v", target, err)
|
||||
_, _ = portal.MainIntent().KickUser(portal.MXID, &mautrix.ReqKickUser{UserID: target})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (portal *Portal) HandleWhatsAppKick(source *User, senderJID types.JID, jids []types.JID) {
|
||||
sender := portal.bridge.GetPuppetByJID(senderJID)
|
||||
senderIntent := sender.IntentFor(portal)
|
||||
for _, jid := range jids {
|
||||
if source != nil && source.JID.User == jid.User {
|
||||
portal.log.Debugln("Ignoring self-kick by", source.MXID)
|
||||
continue
|
||||
}
|
||||
puppet := portal.bridge.GetPuppetByJID(jid)
|
||||
portal.removeUser(puppet.JID == sender.JID, senderIntent, puppet.MXID, puppet.DefaultIntent())
|
||||
|
||||
if !portal.IsBroadcastList() {
|
||||
user := portal.bridge.GetUserByJID(jid)
|
||||
if user != nil {
|
||||
var customIntent *appservice.IntentAPI
|
||||
if puppet.CustomMXID == user.MXID {
|
||||
customIntent = puppet.CustomIntent()
|
||||
}
|
||||
portal.removeUser(puppet.JID == sender.JID, senderIntent, user.MXID, customIntent)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (portal *Portal) leaveWithPuppetMeta(intent *appservice.IntentAPI) (*mautrix.RespSendEvent, error) {
|
||||
content := event.Content{
|
||||
Parsed: event.MemberEventContent{
|
||||
Membership: event.MembershipLeave,
|
||||
},
|
||||
Raw: map[string]interface{}{
|
||||
"net.maunium.whatsapp.puppet": true,
|
||||
},
|
||||
}
|
||||
return intent.SendStateEvent(portal.MXID, event.StateMember, intent.UserID.String(), &content)
|
||||
}
|
||||
|
||||
func (portal *Portal) HandleWhatsAppInvite(source *User, senderJID *types.JID, jids []types.JID) (evtID id.EventID) {
|
||||
intent := portal.MainIntent()
|
||||
if senderJID != nil && !senderJID.IsEmpty() {
|
||||
sender := portal.bridge.GetPuppetByJID(*senderJID)
|
||||
intent = sender.IntentFor(portal)
|
||||
}
|
||||
for _, jid := range jids {
|
||||
puppet := portal.bridge.GetPuppetByJID(jid)
|
||||
puppet.SyncContact(source, true)
|
||||
content := event.Content{
|
||||
Parsed: event.MemberEventContent{
|
||||
Membership: "invite",
|
||||
Displayname: puppet.Displayname,
|
||||
AvatarURL: puppet.AvatarURL.CUString(),
|
||||
},
|
||||
Raw: map[string]interface{}{
|
||||
"net.maunium.whatsapp.puppet": true,
|
||||
},
|
||||
}
|
||||
resp, err := intent.SendStateEvent(portal.MXID, event.StateMember, puppet.MXID.String(), &content)
|
||||
if err != nil {
|
||||
portal.log.Warnfln("Failed to invite %s as %s: %v", puppet.MXID, intent.UserID, err)
|
||||
_ = portal.MainIntent().EnsureInvited(portal.MXID, puppet.MXID)
|
||||
} else {
|
||||
evtID = resp.EventID
|
||||
}
|
||||
err = puppet.DefaultIntent().EnsureJoined(portal.MXID)
|
||||
if err != nil {
|
||||
portal.log.Errorfln("Failed to ensure %s is joined: %v", puppet.MXID, err)
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (portal *Portal) makeMediaBridgeFailureMessage(intent *appservice.IntentAPI, info *types.MessageInfo, bridgeErr error, captionContent *event.MessageEventContent) *ConvertedMessage {
|
||||
portal.log.Errorfln("Failed to bridge media for %s: %v", info.ID, bridgeErr)
|
||||
|
|
213
user.go
213
user.go
|
@ -376,13 +376,31 @@ func (user *User) handleHistorySync(evt *waProto.HistorySync) {
|
|||
continue
|
||||
}
|
||||
|
||||
portal := user.GetPortalByJID(jid)
|
||||
err = portal.CreateMatrixRoom(user)
|
||||
if err != nil {
|
||||
user.log.Warnfln("Failed to create room for %s during backfill: %v", portal.Key.JID, err)
|
||||
continue
|
||||
muteEnd := time.Unix(int64(conv.GetMuteEndTime()), 0)
|
||||
if muteEnd.After(time.Now()) {
|
||||
_ = user.Client.Store.ChatSettings.PutMutedUntil(jid, muteEnd)
|
||||
}
|
||||
if conv.GetArchived() {
|
||||
_ = user.Client.Store.ChatSettings.PutArchived(jid, true)
|
||||
}
|
||||
if conv.GetPinned() > 0 {
|
||||
_ = user.Client.Store.ChatSettings.PutPinned(jid, true)
|
||||
}
|
||||
|
||||
portal := user.GetPortalByJID(jid)
|
||||
if user.bridge.Config.Bridge.HistorySync.CreatePortals {
|
||||
err = portal.CreateMatrixRoom(user)
|
||||
if err != nil {
|
||||
user.log.Warnfln("Failed to create room for %s during backfill: %v", portal.Key.JID, err)
|
||||
continue
|
||||
}
|
||||
}
|
||||
if len(portal.MXID) > 0 && user.bridge.Config.Bridge.HistorySync.Backfill {
|
||||
portal.backfill(user, conv.GetMessages())
|
||||
if !conv.GetMarkedAsUnread() && conv.GetUnreadCount() == 0 {
|
||||
user.markSelfReadFull(portal)
|
||||
}
|
||||
}
|
||||
portal.backfill(user, conv.GetMessages())
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -432,6 +450,10 @@ func (user *User) HandleEvent(event interface{}) {
|
|||
go user.syncPuppet(v.JID)
|
||||
case *events.PushName:
|
||||
go user.syncPuppet(v.JID)
|
||||
case *events.GroupInfo:
|
||||
go user.handleGroupUpdate(v)
|
||||
case *events.Picture:
|
||||
go user.handlePictureUpdate(v)
|
||||
case *events.Receipt:
|
||||
go user.handleReceipt(v)
|
||||
case *events.ChatPresence:
|
||||
|
@ -540,29 +562,21 @@ type CustomReadReceipt struct {
|
|||
DoublePuppet bool `json:"net.maunium.whatsapp.puppet,omitempty"`
|
||||
}
|
||||
|
||||
func (user *User) syncChatDoublePuppetDetails(doublePuppet *Puppet, portal *Portal, justCreated bool) {
|
||||
func (user *User) syncChatDoublePuppetDetails(portal *Portal, justCreated bool) {
|
||||
doublePuppet := portal.bridge.GetPuppetByCustomMXID(user.MXID)
|
||||
if doublePuppet == nil {
|
||||
return
|
||||
}
|
||||
if doublePuppet == nil || doublePuppet.CustomIntent() == nil || len(portal.MXID) == 0 {
|
||||
return
|
||||
}
|
||||
intent := doublePuppet.CustomIntent()
|
||||
// FIXME this might not be possible to do anymore
|
||||
//if chat.UnreadCount == 0 && (justCreated || !user.bridge.Config.Bridge.MarkReadOnlyOnCreate) {
|
||||
// lastMessage := user.bridge.DB.Message.GetLastInChatBefore(chat.Portal.Key, chat.ReceivedAt.Unix())
|
||||
// if lastMessage != nil {
|
||||
// err := intent.MarkReadWithContent(chat.Portal.MXID, lastMessage.MXID, &CustomReadReceipt{DoublePuppet: true})
|
||||
// if err != nil {
|
||||
// user.log.Warnfln("Failed to mark %s in %s as read after backfill: %v", lastMessage.MXID, chat.Portal.MXID, err)
|
||||
// }
|
||||
// }
|
||||
//} else if chat.UnreadCount == -1 {
|
||||
// user.log.Debugfln("Invalid unread count (missing field?) in chat info %+v", chat.Source)
|
||||
//}
|
||||
if justCreated || !user.bridge.Config.Bridge.TagOnlyOnCreate {
|
||||
chat, err := user.Client.Store.ChatSettings.GetChatSettings(portal.Key.JID)
|
||||
if err != nil {
|
||||
user.log.Warnfln("Failed to get settings of %s: %v", portal.Key.JID, err)
|
||||
return
|
||||
}
|
||||
intent := doublePuppet.CustomIntent()
|
||||
user.updateChatMute(intent, portal, chat.MutedUntil)
|
||||
user.updateChatTag(intent, portal, user.bridge.Config.Bridge.ArchiveTag, chat.Archived)
|
||||
user.updateChatTag(intent, portal, user.bridge.Config.Bridge.PinnedTag, chat.Pinned)
|
||||
|
@ -709,12 +723,8 @@ func (user *User) markOtherRead(portal *Portal, intent *appservice.IntentAPI, me
|
|||
}
|
||||
|
||||
func (user *User) markSelfRead(portal *Portal, messageID types.MessageID) {
|
||||
puppet := user.bridge.GetPuppetByJID(user.JID)
|
||||
if puppet == nil {
|
||||
return
|
||||
}
|
||||
intent := puppet.CustomIntent()
|
||||
if intent == nil {
|
||||
puppet := user.bridge.GetPuppetByCustomMXID(user.MXID)
|
||||
if puppet == nil || puppet.CustomIntent() == nil {
|
||||
return
|
||||
}
|
||||
var message *database.Message
|
||||
|
@ -731,106 +741,69 @@ func (user *User) markSelfRead(portal *Portal, messageID types.MessageID) {
|
|||
}
|
||||
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.MarkReadWithContent(portal.MXID, message.MXID, &CustomReadReceipt{DoublePuppet: true})
|
||||
err := puppet.CustomIntent().MarkReadWithContent(portal.MXID, message.MXID, &CustomReadReceipt{DoublePuppet: true})
|
||||
if err != nil {
|
||||
user.log.Warnfln("Failed to bridge own read receipt in %s: %v", portal.Key.JID, err)
|
||||
}
|
||||
}
|
||||
|
||||
//func (user *User) HandleCommand(cmd whatsapp.JSONCommand) {
|
||||
// switch cmd.Type {
|
||||
// case whatsapp.CommandPicture:
|
||||
// if strings.HasSuffix(cmd.JID, whatsapp.NewUserSuffix) {
|
||||
// puppet := user.bridge.GetPuppetByJID(cmd.JID)
|
||||
// go puppet.UpdateAvatar(user, cmd.ProfilePicInfo)
|
||||
// } else if user.bridge.Config.Bridge.ChatMetaSync {
|
||||
// portal := user.GetPortalByJID(cmd.JID)
|
||||
// go portal.UpdateAvatar(user, cmd.ProfilePicInfo, true)
|
||||
// }
|
||||
// case whatsapp.CommandDisconnect:
|
||||
// if cmd.Kind == "replaced" {
|
||||
// user.cleanDisconnection = true
|
||||
// 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) markSelfReadFull(portal *Portal) {
|
||||
puppet := user.bridge.GetPuppetByCustomMXID(user.MXID)
|
||||
if puppet == nil || puppet.CustomIntent() == nil {
|
||||
return
|
||||
}
|
||||
lastMessage := user.bridge.DB.Message.GetLastInChat(portal.Key)
|
||||
if lastMessage == nil {
|
||||
return
|
||||
}
|
||||
err := puppet.CustomIntent().MarkReadWithContent(portal.MXID, lastMessage.MXID, &CustomReadReceipt{DoublePuppet: true})
|
||||
if err != nil {
|
||||
user.log.Warnfln("Failed to mark %s in %s as read after backfill: %v", lastMessage.MXID, portal.MXID, err)
|
||||
}
|
||||
}
|
||||
|
||||
//func (user *User) HandleChatUpdate(cmd whatsapp.ChatUpdate) {
|
||||
// if cmd.Command != whatsapp.ChatUpdateCommandAction {
|
||||
// return
|
||||
// }
|
||||
//
|
||||
// portal := user.GetPortalByJID(cmd.JID)
|
||||
// if len(portal.MXID) == 0 {
|
||||
// if cmd.Data.Action == whatsapp.ChatActionIntroduce || cmd.Data.Action == whatsapp.ChatActionCreate {
|
||||
// go func() {
|
||||
// err := portal.CreateMatrixRoom(user)
|
||||
// if err != nil {
|
||||
// user.log.Errorln("Failed to create portal room after receiving join event:", err)
|
||||
// }
|
||||
// }()
|
||||
// }
|
||||
// return
|
||||
// }
|
||||
//
|
||||
// // These don't come down the message history :(
|
||||
// switch cmd.Data.Action {
|
||||
// case whatsapp.ChatActionAddTopic:
|
||||
// go portal.UpdateTopic(cmd.Data.AddTopic.Topic, cmd.Data.SenderJID, nil, true)
|
||||
// case whatsapp.ChatActionRemoveTopic:
|
||||
// go portal.UpdateTopic("", cmd.Data.SenderJID, nil, true)
|
||||
// case whatsapp.ChatActionRemove:
|
||||
// // We ignore leaving groups in the message history to avoid accidentally leaving rejoined groups,
|
||||
// // but if we get a real-time command that says we left, it should be safe to bridge it.
|
||||
// if !user.bridge.Config.Bridge.ChatMetaSync {
|
||||
// for _, jid := range cmd.Data.UserChange.JIDs {
|
||||
// if jid == user.JID {
|
||||
// go portal.HandleWhatsAppKick(nil, cmd.Data.SenderJID, cmd.Data.UserChange.JIDs)
|
||||
// break
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// if !user.bridge.Config.Bridge.ChatMetaSync {
|
||||
// // Ignore chat update commands, we're relying on the message history.
|
||||
// return
|
||||
// }
|
||||
//
|
||||
// switch cmd.Data.Action {
|
||||
// case whatsapp.ChatActionNameChange:
|
||||
// go portal.UpdateName(cmd.Data.NameChange.Name, cmd.Data.SenderJID, nil, true)
|
||||
// case whatsapp.ChatActionPromote:
|
||||
// go portal.ChangeAdminStatus(cmd.Data.UserChange.JIDs, true)
|
||||
// case whatsapp.ChatActionDemote:
|
||||
// go portal.ChangeAdminStatus(cmd.Data.UserChange.JIDs, false)
|
||||
// case whatsapp.ChatActionAnnounce:
|
||||
// go portal.RestrictMessageSending(cmd.Data.Announce)
|
||||
// case whatsapp.ChatActionRestrict:
|
||||
// go portal.RestrictMetadataChanges(cmd.Data.Restrict)
|
||||
// case whatsapp.ChatActionRemove:
|
||||
// go portal.HandleWhatsAppKick(nil, cmd.Data.SenderJID, cmd.Data.UserChange.JIDs)
|
||||
// case whatsapp.ChatActionAdd:
|
||||
// go portal.HandleWhatsAppInvite(user, cmd.Data.SenderJID, nil, cmd.Data.UserChange.JIDs)
|
||||
// case whatsapp.ChatActionIntroduce:
|
||||
// if cmd.Data.SenderJID != "unknown" {
|
||||
// go portal.Sync(user, whatsapp.Contact{JID: portal.Key.JID})
|
||||
// }
|
||||
// }
|
||||
//}
|
||||
//
|
||||
//func (user *User) HandleJSONMessage(evt whatsapp.RawJSONMessage) {
|
||||
// if !json.Valid(evt.RawMessage) {
|
||||
// return
|
||||
// }
|
||||
// user.log.Debugfln("JSON message with tag %s: %s", evt.Tag, evt.RawMessage)
|
||||
// user.updateLastConnectionIfNecessary()
|
||||
//}
|
||||
func (user *User) handleGroupUpdate(evt *events.GroupInfo) {
|
||||
portal := user.GetPortalByJID(evt.JID)
|
||||
if portal == nil || len(portal.MXID) == 0 {
|
||||
// TODO create portal when added to group
|
||||
user.log.Debugfln("Ignoring group info update in chat with no portal: %+v", evt)
|
||||
return
|
||||
}
|
||||
switch {
|
||||
case evt.Announce != nil:
|
||||
portal.RestrictMessageSending(evt.Announce.IsAnnounce)
|
||||
case evt.Locked != nil:
|
||||
portal.RestrictMetadataChanges(evt.Locked.IsLocked)
|
||||
case evt.Name != nil:
|
||||
portal.UpdateName(evt.Name.Name, evt.Name.NameSetBy, true)
|
||||
case evt.Topic != nil:
|
||||
portal.UpdateTopic(evt.Topic.Topic, evt.Topic.TopicSetBy, true)
|
||||
case evt.Leave != nil:
|
||||
if evt.Sender != nil && !evt.Sender.IsEmpty() {
|
||||
portal.HandleWhatsAppKick(user, *evt.Sender, evt.Leave)
|
||||
}
|
||||
case evt.Join != nil:
|
||||
portal.HandleWhatsAppInvite(user, evt.Sender, evt.Join)
|
||||
case evt.Promote != nil:
|
||||
portal.ChangeAdminStatus(evt.Promote, true)
|
||||
case evt.Demote != nil:
|
||||
portal.ChangeAdminStatus(evt.Demote, false)
|
||||
}
|
||||
}
|
||||
|
||||
func (user *User) handlePictureUpdate(evt *events.Picture) {
|
||||
if evt.JID.Server == types.DefaultUserServer {
|
||||
puppet := user.bridge.GetPuppetByJID(evt.JID)
|
||||
if puppet.Avatar != evt.PictureID {
|
||||
puppet.UpdateAvatar(user)
|
||||
}
|
||||
} else {
|
||||
portal := user.GetPortalByJID(evt.JID)
|
||||
if portal != nil && portal.Avatar != evt.PictureID {
|
||||
portal.UpdateAvatar(user, evt.Author, true)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (user *User) NeedsRelaybot(portal *Portal) bool {
|
||||
return !user.HasSession() // || !user.IsInPortal(portal.Key)
|
||||
|
|
Loading…
Reference in a new issue