github.com/status-im/status-go@v1.1.0/protocol/communities/community_changes.go (about) 1 package communities 2 3 import ( 4 "crypto/ecdsa" 5 6 slices "golang.org/x/exp/slices" 7 8 "github.com/status-im/status-go/protocol/protobuf" 9 ) 10 11 type CommunityChatChanges struct { 12 ChatModified *protobuf.CommunityChat 13 MembersAdded map[string]*protobuf.CommunityMember 14 MembersRemoved map[string]*protobuf.CommunityMember 15 CategoryModified string 16 PositionModified int 17 FirstMessageTimestampModified uint32 18 } 19 20 type CommunityChanges struct { 21 Community *Community `json:"community"` 22 23 ControlNodeChanged *ecdsa.PublicKey `json:"controlNodeChanged"` 24 25 MembersAdded map[string]*protobuf.CommunityMember `json:"membersAdded"` 26 MembersRemoved map[string]*protobuf.CommunityMember `json:"membersRemoved"` 27 MembersBanned map[string]bool `json:"membersBanned"` 28 MembersUnbanned map[string]bool `json:"membersUnbanned"` 29 30 TokenPermissionsAdded map[string]*CommunityTokenPermission `json:"tokenPermissionsAdded"` 31 TokenPermissionsModified map[string]*CommunityTokenPermission `json:"tokenPermissionsModified"` 32 TokenPermissionsRemoved map[string]*CommunityTokenPermission `json:"tokenPermissionsRemoved"` 33 34 ChatsRemoved map[string]*protobuf.CommunityChat `json:"chatsRemoved"` 35 ChatsAdded map[string]*protobuf.CommunityChat `json:"chatsAdded"` 36 ChatsModified map[string]*CommunityChatChanges `json:"chatsModified"` 37 38 CategoriesRemoved []string `json:"categoriesRemoved"` 39 CategoriesAdded map[string]*protobuf.CommunityCategory `json:"categoriesAdded"` 40 CategoriesModified map[string]*protobuf.CommunityCategory `json:"categoriesModified"` 41 42 MemberWalletsRemoved []string `json:"memberWalletsRemoved"` 43 MemberWalletsAdded map[string][]*protobuf.RevealedAccount `json:"memberWalletsAdded"` 44 45 // ShouldMemberJoin indicates whether the user should join this community 46 // automatically 47 ShouldMemberJoin bool `json:"memberAdded"` 48 49 // MemberKicked indicates whether the user has been kicked out 50 MemberKicked bool `json:"memberRemoved"` 51 52 // MemberSoftKicked indicates whether the user has been kicked out due to lack of specific data 53 // No kick AC notification will be generated and member will join automatically 54 // as soon as he provides missing data 55 MemberSoftKicked bool `json:"memberSoftRemoved"` 56 } 57 58 func EmptyCommunityChanges() *CommunityChanges { 59 return &CommunityChanges{ 60 MembersAdded: make(map[string]*protobuf.CommunityMember), 61 MembersRemoved: make(map[string]*protobuf.CommunityMember), 62 MembersBanned: make(map[string]bool), 63 MembersUnbanned: make(map[string]bool), 64 65 TokenPermissionsAdded: make(map[string]*CommunityTokenPermission), 66 TokenPermissionsModified: make(map[string]*CommunityTokenPermission), 67 TokenPermissionsRemoved: make(map[string]*CommunityTokenPermission), 68 69 ChatsRemoved: make(map[string]*protobuf.CommunityChat), 70 ChatsAdded: make(map[string]*protobuf.CommunityChat), 71 ChatsModified: make(map[string]*CommunityChatChanges), 72 73 CategoriesRemoved: []string{}, 74 CategoriesAdded: make(map[string]*protobuf.CommunityCategory), 75 CategoriesModified: make(map[string]*protobuf.CommunityCategory), 76 77 MemberWalletsRemoved: []string{}, 78 MemberWalletsAdded: make(map[string][]*protobuf.RevealedAccount), 79 } 80 } 81 82 func (c *CommunityChanges) Merge(other *CommunityChanges) { 83 for memberID, member := range other.MembersAdded { 84 c.MembersAdded[memberID] = member 85 } 86 for memberID := range other.MembersRemoved { 87 c.MembersRemoved[memberID] = other.MembersRemoved[memberID] 88 } 89 for memberID, banned := range other.MembersBanned { 90 c.MembersBanned[memberID] = banned 91 } 92 for memberID, unbanned := range other.MembersUnbanned { 93 c.MembersUnbanned[memberID] = unbanned 94 } 95 for permissionID, permission := range other.TokenPermissionsAdded { 96 c.TokenPermissionsAdded[permissionID] = permission 97 } 98 for permissionID, permission := range other.TokenPermissionsModified { 99 c.TokenPermissionsModified[permissionID] = permission 100 } 101 for permissionID, permission := range other.TokenPermissionsRemoved { 102 c.TokenPermissionsRemoved[permissionID] = permission 103 } 104 for chatID, chat := range other.ChatsRemoved { 105 c.ChatsRemoved[chatID] = chat 106 } 107 for chatID, chat := range other.ChatsAdded { 108 c.ChatsAdded[chatID] = chat 109 } 110 for chatID, changes := range other.ChatsModified { 111 c.ChatsModified[chatID] = changes 112 } 113 114 c.CategoriesRemoved = append(c.CategoriesRemoved, other.CategoriesRemoved...) 115 116 for categoryID, category := range other.CategoriesAdded { 117 c.CategoriesAdded[categoryID] = category 118 } 119 for categoryID, category := range other.CategoriesModified { 120 c.CategoriesModified[categoryID] = category 121 } 122 123 c.MemberWalletsRemoved = append(c.MemberWalletsRemoved, other.MemberWalletsRemoved...) 124 125 for walletID, wallets := range other.MemberWalletsAdded { 126 c.MemberWalletsAdded[walletID] = wallets 127 } 128 } 129 130 func (c *CommunityChanges) HasNewMember(identity string) bool { 131 if len(c.MembersAdded) == 0 { 132 return false 133 } 134 _, ok := c.MembersAdded[identity] 135 return ok 136 } 137 138 func (c *CommunityChanges) HasMemberLeft(identity string) bool { 139 if len(c.MembersRemoved) == 0 { 140 return false 141 } 142 _, ok := c.MembersRemoved[identity] 143 return ok 144 } 145 146 func (c *CommunityChanges) IsMemberBanned(identity string) bool { 147 if len(c.MembersBanned) == 0 { 148 return false 149 } 150 _, ok := c.MembersBanned[identity] 151 return ok 152 } 153 154 func (c *CommunityChanges) IsMemberUnbanned(identity string) bool { 155 if len(c.MembersUnbanned) == 0 { 156 return false 157 } 158 _, ok := c.MembersUnbanned[identity] 159 return ok 160 } 161 162 func EvaluateCommunityChanges(origin, modified *Community) *CommunityChanges { 163 changes := evaluateCommunityChangesByDescription(origin.Description(), modified.Description()) 164 165 if origin.ControlNode() != nil && !modified.ControlNode().Equal(origin.ControlNode()) { 166 changes.ControlNodeChanged = modified.ControlNode() 167 } 168 169 originTokenPermissions := origin.tokenPermissions() 170 modifiedTokenPermissions := modified.tokenPermissions() 171 172 // Check for modified or removed token permissions 173 for id, originPermission := range originTokenPermissions { 174 if modifiedPermission := modifiedTokenPermissions[id]; modifiedPermission != nil { 175 if !modifiedPermission.Equals(originPermission) { 176 changes.TokenPermissionsModified[id] = modifiedPermission 177 } 178 } else { 179 changes.TokenPermissionsRemoved[id] = originPermission 180 } 181 } 182 183 // Check for added token permissions 184 for id, permission := range modifiedTokenPermissions { 185 if _, ok := originTokenPermissions[id]; !ok { 186 changes.TokenPermissionsAdded[id] = permission 187 } 188 } 189 190 changes.Community = modified 191 return changes 192 } 193 194 func evaluateCommunityChangesByDescription(origin, modified *protobuf.CommunityDescription) *CommunityChanges { 195 changes := EmptyCommunityChanges() 196 197 // Check for new members at the org level 198 for pk, member := range modified.Members { 199 if _, ok := origin.Members[pk]; !ok { 200 changes.MembersAdded[pk] = member 201 } 202 } 203 204 // Check ban/unban 205 findDiffInBannedMembers(modified.BannedMembers, origin.BannedMembers, changes.MembersBanned) 206 findDiffInBannedMembers(origin.BannedMembers, modified.BannedMembers, changes.MembersUnbanned) 207 208 // Check for new banned members (from deprecated BanList) 209 findDiffInBanList(modified.BanList, origin.BanList, changes.MembersBanned) 210 211 // Check for new unbanned members (from deprecated BanList) 212 findDiffInBanList(origin.BanList, modified.BanList, changes.MembersUnbanned) 213 214 // Check for removed members at the org level 215 for pk, member := range origin.Members { 216 if _, ok := modified.Members[pk]; !ok { 217 changes.MembersRemoved[pk] = member 218 } 219 } 220 221 // check for removed chats 222 for chatID, chat := range origin.Chats { 223 if modified.Chats == nil { 224 modified.Chats = make(map[string]*protobuf.CommunityChat) 225 } 226 if _, ok := modified.Chats[chatID]; !ok { 227 changes.ChatsRemoved[chatID] = chat 228 } 229 } 230 231 for chatID, chat := range modified.Chats { 232 if origin.Chats == nil { 233 origin.Chats = make(map[string]*protobuf.CommunityChat) 234 } 235 236 if _, ok := origin.Chats[chatID]; !ok { 237 changes.ChatsAdded[chatID] = chat 238 } else { 239 240 // Check for members added 241 for pk, member := range modified.Chats[chatID].Members { 242 if _, ok := origin.Chats[chatID].Members[pk]; !ok { 243 if changes.ChatsModified[chatID] == nil { 244 changes.ChatsModified[chatID] = &CommunityChatChanges{ 245 MembersAdded: make(map[string]*protobuf.CommunityMember), 246 MembersRemoved: make(map[string]*protobuf.CommunityMember), 247 } 248 } 249 changes.ChatsModified[chatID].MembersAdded[pk] = member 250 } 251 } 252 253 // check for members removed 254 for pk, member := range origin.Chats[chatID].Members { 255 if _, ok := modified.Chats[chatID].Members[pk]; !ok { 256 if changes.ChatsModified[chatID] == nil { 257 changes.ChatsModified[chatID] = &CommunityChatChanges{ 258 MembersAdded: make(map[string]*protobuf.CommunityMember), 259 MembersRemoved: make(map[string]*protobuf.CommunityMember), 260 } 261 } 262 changes.ChatsModified[chatID].MembersRemoved[pk] = member 263 } 264 } 265 266 // check if first message timestamp was modified 267 if origin.Chats[chatID].Identity.FirstMessageTimestamp != 268 modified.Chats[chatID].Identity.FirstMessageTimestamp { 269 if changes.ChatsModified[chatID] == nil { 270 changes.ChatsModified[chatID] = &CommunityChatChanges{ 271 MembersAdded: make(map[string]*protobuf.CommunityMember), 272 MembersRemoved: make(map[string]*protobuf.CommunityMember), 273 } 274 } 275 changes.ChatsModified[chatID].FirstMessageTimestampModified = modified.Chats[chatID].Identity.FirstMessageTimestamp 276 } 277 } 278 } 279 280 // Check for categories that were removed 281 for categoryID := range origin.Categories { 282 if modified.Categories == nil { 283 modified.Categories = make(map[string]*protobuf.CommunityCategory) 284 } 285 286 if modified.Chats == nil { 287 modified.Chats = make(map[string]*protobuf.CommunityChat) 288 } 289 290 if _, ok := modified.Categories[categoryID]; !ok { 291 changes.CategoriesRemoved = append(changes.CategoriesRemoved, categoryID) 292 } 293 294 if origin.Chats == nil { 295 origin.Chats = make(map[string]*protobuf.CommunityChat) 296 } 297 } 298 299 // Check for categories that were added 300 for categoryID, category := range modified.Categories { 301 if origin.Categories == nil { 302 origin.Categories = make(map[string]*protobuf.CommunityCategory) 303 } 304 if _, ok := origin.Categories[categoryID]; !ok { 305 changes.CategoriesAdded[categoryID] = category 306 } else { 307 if origin.Categories[categoryID].Name != category.Name || origin.Categories[categoryID].Position != category.Position { 308 changes.CategoriesModified[categoryID] = category 309 } 310 } 311 } 312 313 // Check for chat categories that were modified 314 for chatID, chat := range modified.Chats { 315 if origin.Chats == nil { 316 origin.Chats = make(map[string]*protobuf.CommunityChat) 317 } 318 319 if _, ok := origin.Chats[chatID]; !ok { 320 continue // It's a new chat 321 } 322 323 if origin.Chats[chatID].CategoryId != chat.CategoryId { 324 if changes.ChatsModified[chatID] == nil { 325 changes.ChatsModified[chatID] = &CommunityChatChanges{ 326 MembersAdded: make(map[string]*protobuf.CommunityMember), 327 MembersRemoved: make(map[string]*protobuf.CommunityMember), 328 } 329 } 330 331 changes.ChatsModified[chatID].CategoryModified = chat.CategoryId 332 } 333 } 334 335 return changes 336 } 337 338 func findDiffInBanList(searchFrom []string, searchIn []string, storeTo map[string]bool) { 339 for _, memberToFind := range searchFrom { 340 if _, stored := storeTo[memberToFind]; stored { 341 continue 342 } 343 344 exists := slices.Contains(searchIn, memberToFind) 345 346 if !exists { 347 storeTo[memberToFind] = false 348 } 349 } 350 } 351 352 func findDiffInBannedMembers(searchFrom map[string]*protobuf.CommunityBanInfo, searchIn map[string]*protobuf.CommunityBanInfo, storeTo map[string]bool) { 353 if searchFrom == nil { 354 return 355 } else if searchIn == nil { 356 for memberToFind, value := range searchFrom { 357 storeTo[memberToFind] = value.DeleteAllMessages 358 } 359 } else { 360 for memberToFind, value := range searchFrom { 361 if _, exists := searchIn[memberToFind]; !exists { 362 storeTo[memberToFind] = value.DeleteAllMessages 363 } 364 } 365 } 366 }