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  }